diff --git a/.gitignore b/.gitignore index 624533d4..6fcc1a0f 100644 --- a/.gitignore +++ b/.gitignore @@ -81,7 +81,6 @@ docs/.cache/ # Nuxt.js build / generate output .nuxt -dist lib public/ docs/public/ diff --git a/dist/index-Ck-r09gt.js b/dist/index-Ck-r09gt.js new file mode 100644 index 00000000..050dd48d --- /dev/null +++ b/dist/index-Ck-r09gt.js @@ -0,0 +1,7371 @@ +/* @thoughtspot/visual-embed-sdk version 1.48.0 */ +'use client'; +const ALIAS = Symbol.for('yaml.alias'); +const DOC = Symbol.for('yaml.document'); +const MAP = Symbol.for('yaml.map'); +const PAIR = Symbol.for('yaml.pair'); +const SCALAR$1 = Symbol.for('yaml.scalar'); +const SEQ = Symbol.for('yaml.seq'); +const NODE_TYPE = Symbol.for('yaml.node.type'); +const isAlias = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === ALIAS; +const isDocument = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === DOC; +const isMap = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === MAP; +const isPair = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === PAIR; +const isScalar$1 = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === SCALAR$1; +const isSeq = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === SEQ; +function isCollection$1(node) { + if (node && typeof node === 'object') + switch (node[NODE_TYPE]) { + case MAP: + case SEQ: + return true; + } + return false; +} +function isNode(node) { + if (node && typeof node === 'object') + switch (node[NODE_TYPE]) { + case ALIAS: + case MAP: + case SCALAR$1: + case SEQ: + return true; + } + return false; +} +const hasAnchor = (node) => (isScalar$1(node) || isCollection$1(node)) && !!node.anchor; + +const BREAK$1 = Symbol('break visit'); +const SKIP$1 = Symbol('skip children'); +const REMOVE$1 = Symbol('remove node'); +/** + * Apply a visitor to an AST node or document. + * + * Walks through the tree (depth-first) starting from `node`, calling a + * `visitor` function with three arguments: + * - `key`: For sequence values and map `Pair`, the node's index in the + * collection. Within a `Pair`, `'key'` or `'value'`, correspondingly. + * `null` for the root node. + * - `node`: The current node. + * - `path`: The ancestry of the current node. + * + * The return value of the visitor may be used to control the traversal: + * - `undefined` (default): Do nothing and continue + * - `visit.SKIP`: Do not visit the children of this node, continue with next + * sibling + * - `visit.BREAK`: Terminate traversal completely + * - `visit.REMOVE`: Remove the current node, then continue with the next one + * - `Node`: Replace the current node, then continue by visiting it + * - `number`: While iterating the items of a sequence or map, set the index + * of the next step. This is useful especially if the index of the current + * node has changed. + * + * If `visitor` is a single function, it will be called with all values + * encountered in the tree, including e.g. `null` values. Alternatively, + * separate visitor functions may be defined for each `Map`, `Pair`, `Seq`, + * `Alias` and `Scalar` node. To define the same visitor function for more than + * one node type, use the `Collection` (map and seq), `Value` (map, seq & scalar) + * and `Node` (alias, map, seq & scalar) targets. Of all these, only the most + * specific defined one will be used for each node. + */ +function visit$1(node, visitor) { + const visitor_ = initVisitor(visitor); + if (isDocument(node)) { + const cd = visit_(null, node.contents, visitor_, Object.freeze([node])); + if (cd === REMOVE$1) + node.contents = null; + } + else + visit_(null, node, visitor_, Object.freeze([])); +} +// Without the `as symbol` casts, TS declares these in the `visit` +// namespace using `var`, but then complains about that because +// `unique symbol` must be `const`. +/** Terminate visit traversal completely */ +visit$1.BREAK = BREAK$1; +/** Do not visit the children of the current node */ +visit$1.SKIP = SKIP$1; +/** Remove the current node */ +visit$1.REMOVE = REMOVE$1; +function visit_(key, node, visitor, path) { + const ctrl = callVisitor(key, node, visitor, path); + if (isNode(ctrl) || isPair(ctrl)) { + replaceNode(key, path, ctrl); + return visit_(key, ctrl, visitor, path); + } + if (typeof ctrl !== 'symbol') { + if (isCollection$1(node)) { + path = Object.freeze(path.concat(node)); + for (let i = 0; i < node.items.length; ++i) { + const ci = visit_(i, node.items[i], visitor, path); + if (typeof ci === 'number') + i = ci - 1; + else if (ci === BREAK$1) + return BREAK$1; + else if (ci === REMOVE$1) { + node.items.splice(i, 1); + i -= 1; + } + } + } + else if (isPair(node)) { + path = Object.freeze(path.concat(node)); + const ck = visit_('key', node.key, visitor, path); + if (ck === BREAK$1) + return BREAK$1; + else if (ck === REMOVE$1) + node.key = null; + const cv = visit_('value', node.value, visitor, path); + if (cv === BREAK$1) + return BREAK$1; + else if (cv === REMOVE$1) + node.value = null; + } + } + return ctrl; +} +/** + * Apply an async visitor to an AST node or document. + * + * Walks through the tree (depth-first) starting from `node`, calling a + * `visitor` function with three arguments: + * - `key`: For sequence values and map `Pair`, the node's index in the + * collection. Within a `Pair`, `'key'` or `'value'`, correspondingly. + * `null` for the root node. + * - `node`: The current node. + * - `path`: The ancestry of the current node. + * + * The return value of the visitor may be used to control the traversal: + * - `Promise`: Must resolve to one of the following values + * - `undefined` (default): Do nothing and continue + * - `visit.SKIP`: Do not visit the children of this node, continue with next + * sibling + * - `visit.BREAK`: Terminate traversal completely + * - `visit.REMOVE`: Remove the current node, then continue with the next one + * - `Node`: Replace the current node, then continue by visiting it + * - `number`: While iterating the items of a sequence or map, set the index + * of the next step. This is useful especially if the index of the current + * node has changed. + * + * If `visitor` is a single function, it will be called with all values + * encountered in the tree, including e.g. `null` values. Alternatively, + * separate visitor functions may be defined for each `Map`, `Pair`, `Seq`, + * `Alias` and `Scalar` node. To define the same visitor function for more than + * one node type, use the `Collection` (map and seq), `Value` (map, seq & scalar) + * and `Node` (alias, map, seq & scalar) targets. Of all these, only the most + * specific defined one will be used for each node. + */ +async function visitAsync(node, visitor) { + const visitor_ = initVisitor(visitor); + if (isDocument(node)) { + const cd = await visitAsync_(null, node.contents, visitor_, Object.freeze([node])); + if (cd === REMOVE$1) + node.contents = null; + } + else + await visitAsync_(null, node, visitor_, Object.freeze([])); +} +// Without the `as symbol` casts, TS declares these in the `visit` +// namespace using `var`, but then complains about that because +// `unique symbol` must be `const`. +/** Terminate visit traversal completely */ +visitAsync.BREAK = BREAK$1; +/** Do not visit the children of the current node */ +visitAsync.SKIP = SKIP$1; +/** Remove the current node */ +visitAsync.REMOVE = REMOVE$1; +async function visitAsync_(key, node, visitor, path) { + const ctrl = await callVisitor(key, node, visitor, path); + if (isNode(ctrl) || isPair(ctrl)) { + replaceNode(key, path, ctrl); + return visitAsync_(key, ctrl, visitor, path); + } + if (typeof ctrl !== 'symbol') { + if (isCollection$1(node)) { + path = Object.freeze(path.concat(node)); + for (let i = 0; i < node.items.length; ++i) { + const ci = await visitAsync_(i, node.items[i], visitor, path); + if (typeof ci === 'number') + i = ci - 1; + else if (ci === BREAK$1) + return BREAK$1; + else if (ci === REMOVE$1) { + node.items.splice(i, 1); + i -= 1; + } + } + } + else if (isPair(node)) { + path = Object.freeze(path.concat(node)); + const ck = await visitAsync_('key', node.key, visitor, path); + if (ck === BREAK$1) + return BREAK$1; + else if (ck === REMOVE$1) + node.key = null; + const cv = await visitAsync_('value', node.value, visitor, path); + if (cv === BREAK$1) + return BREAK$1; + else if (cv === REMOVE$1) + node.value = null; + } + } + return ctrl; +} +function initVisitor(visitor) { + if (typeof visitor === 'object' && + (visitor.Collection || visitor.Node || visitor.Value)) { + return Object.assign({ + Alias: visitor.Node, + Map: visitor.Node, + Scalar: visitor.Node, + Seq: visitor.Node + }, visitor.Value && { + Map: visitor.Value, + Scalar: visitor.Value, + Seq: visitor.Value + }, visitor.Collection && { + Map: visitor.Collection, + Seq: visitor.Collection + }, visitor); + } + return visitor; +} +function callVisitor(key, node, visitor, path) { + if (typeof visitor === 'function') + return visitor(key, node, path); + if (isMap(node)) + return visitor.Map?.(key, node, path); + if (isSeq(node)) + return visitor.Seq?.(key, node, path); + if (isPair(node)) + return visitor.Pair?.(key, node, path); + if (isScalar$1(node)) + return visitor.Scalar?.(key, node, path); + if (isAlias(node)) + return visitor.Alias?.(key, node, path); + return undefined; +} +function replaceNode(key, path, node) { + const parent = path[path.length - 1]; + if (isCollection$1(parent)) { + parent.items[key] = node; + } + else if (isPair(parent)) { + if (key === 'key') + parent.key = node; + else + parent.value = node; + } + else if (isDocument(parent)) { + parent.contents = node; + } + else { + const pt = isAlias(parent) ? 'alias' : 'scalar'; + throw new Error(`Cannot replace node with ${pt} parent`); + } +} + +const escapeChars = { + '!': '%21', + ',': '%2C', + '[': '%5B', + ']': '%5D', + '{': '%7B', + '}': '%7D' +}; +const escapeTagName = (tn) => tn.replace(/[!,[\]{}]/g, ch => escapeChars[ch]); +class Directives { + constructor(yaml, tags) { + /** + * The directives-end/doc-start marker `---`. If `null`, a marker may still be + * included in the document's stringified representation. + */ + this.docStart = null; + /** The doc-end marker `...`. */ + this.docEnd = false; + this.yaml = Object.assign({}, Directives.defaultYaml, yaml); + this.tags = Object.assign({}, Directives.defaultTags, tags); + } + clone() { + const copy = new Directives(this.yaml, this.tags); + copy.docStart = this.docStart; + return copy; + } + /** + * During parsing, get a Directives instance for the current document and + * update the stream state according to the current version's spec. + */ + atDocument() { + const res = new Directives(this.yaml, this.tags); + switch (this.yaml.version) { + case '1.1': + this.atNextDocument = true; + break; + case '1.2': + this.atNextDocument = false; + this.yaml = { + explicit: Directives.defaultYaml.explicit, + version: '1.2' + }; + this.tags = Object.assign({}, Directives.defaultTags); + break; + } + return res; + } + /** + * @param onError - May be called even if the action was successful + * @returns `true` on success + */ + add(line, onError) { + if (this.atNextDocument) { + this.yaml = { explicit: Directives.defaultYaml.explicit, version: '1.1' }; + this.tags = Object.assign({}, Directives.defaultTags); + this.atNextDocument = false; + } + const parts = line.trim().split(/[ \t]+/); + const name = parts.shift(); + switch (name) { + case '%TAG': { + if (parts.length !== 2) { + onError(0, '%TAG directive should contain exactly two parts'); + if (parts.length < 2) + return false; + } + const [handle, prefix] = parts; + this.tags[handle] = prefix; + return true; + } + case '%YAML': { + this.yaml.explicit = true; + if (parts.length !== 1) { + onError(0, '%YAML directive should contain exactly one part'); + return false; + } + const [version] = parts; + if (version === '1.1' || version === '1.2') { + this.yaml.version = version; + return true; + } + else { + const isValid = /^\d+\.\d+$/.test(version); + onError(6, `Unsupported YAML version ${version}`, isValid); + return false; + } + } + default: + onError(0, `Unknown directive ${name}`, true); + return false; + } + } + /** + * Resolves a tag, matching handles to those defined in %TAG directives. + * + * @returns Resolved tag, which may also be the non-specific tag `'!'` or a + * `'!local'` tag, or `null` if unresolvable. + */ + tagName(source, onError) { + if (source === '!') + return '!'; // non-specific tag + if (source[0] !== '!') { + onError(`Not a valid tag: ${source}`); + return null; + } + if (source[1] === '<') { + const verbatim = source.slice(2, -1); + if (verbatim === '!' || verbatim === '!!') { + onError(`Verbatim tags aren't resolved, so ${source} is invalid.`); + return null; + } + if (source[source.length - 1] !== '>') + onError('Verbatim tags must end with a >'); + return verbatim; + } + const [, handle, suffix] = source.match(/^(.*!)([^!]*)$/s); + if (!suffix) + onError(`The ${source} tag has no suffix`); + const prefix = this.tags[handle]; + if (prefix) { + try { + return prefix + decodeURIComponent(suffix); + } + catch (error) { + onError(String(error)); + return null; + } + } + if (handle === '!') + return source; // local tag + onError(`Could not resolve tag: ${source}`); + return null; + } + /** + * Given a fully resolved tag, returns its printable string form, + * taking into account current tag prefixes and defaults. + */ + tagString(tag) { + for (const [handle, prefix] of Object.entries(this.tags)) { + if (tag.startsWith(prefix)) + return handle + escapeTagName(tag.substring(prefix.length)); + } + return tag[0] === '!' ? tag : `!<${tag}>`; + } + toString(doc) { + const lines = this.yaml.explicit + ? [`%YAML ${this.yaml.version || '1.2'}`] + : []; + const tagEntries = Object.entries(this.tags); + let tagNames; + if (doc && tagEntries.length > 0 && isNode(doc.contents)) { + const tags = {}; + visit$1(doc.contents, (_key, node) => { + if (isNode(node) && node.tag) + tags[node.tag] = true; + }); + tagNames = Object.keys(tags); + } + else + tagNames = []; + for (const [handle, prefix] of tagEntries) { + if (handle === '!!' && prefix === 'tag:yaml.org,2002:') + continue; + if (!doc || tagNames.some(tn => tn.startsWith(prefix))) + lines.push(`%TAG ${handle} ${prefix}`); + } + return lines.join('\n'); + } +} +Directives.defaultYaml = { explicit: false, version: '1.2' }; +Directives.defaultTags = { '!!': 'tag:yaml.org,2002:' }; + +/** + * Verify that the input string is a valid anchor. + * + * Will throw on errors. + */ +function anchorIsValid(anchor) { + if (/[\x00-\x19\s,[\]{}]/.test(anchor)) { + const sa = JSON.stringify(anchor); + const msg = `Anchor must not contain whitespace or control characters: ${sa}`; + throw new Error(msg); + } + return true; +} +function anchorNames(root) { + const anchors = new Set(); + visit$1(root, { + Value(_key, node) { + if (node.anchor) + anchors.add(node.anchor); + } + }); + return anchors; +} +/** Find a new anchor name with the given `prefix` and a one-indexed suffix. */ +function findNewAnchor(prefix, exclude) { + for (let i = 1; true; ++i) { + const name = `${prefix}${i}`; + if (!exclude.has(name)) + return name; + } +} +function createNodeAnchors(doc, prefix) { + const aliasObjects = []; + const sourceObjects = new Map(); + let prevAnchors = null; + return { + onAnchor: (source) => { + aliasObjects.push(source); + if (!prevAnchors) + prevAnchors = anchorNames(doc); + const anchor = findNewAnchor(prefix, prevAnchors); + prevAnchors.add(anchor); + return anchor; + }, + /** + * With circular references, the source node is only resolved after all + * of its child nodes are. This is why anchors are set only after all of + * the nodes have been created. + */ + setAnchors: () => { + for (const source of aliasObjects) { + const ref = sourceObjects.get(source); + if (typeof ref === 'object' && + ref.anchor && + (isScalar$1(ref.node) || isCollection$1(ref.node))) { + ref.node.anchor = ref.anchor; + } + else { + const error = new Error('Failed to resolve repeated object (this should not happen)'); + error.source = source; + throw error; + } + } + }, + sourceObjects + }; +} + +/** + * Applies the JSON.parse reviver algorithm as defined in the ECMA-262 spec, + * in section 24.5.1.1 "Runtime Semantics: InternalizeJSONProperty" of the + * 2021 edition: https://tc39.es/ecma262/#sec-json.parse + * + * Includes extensions for handling Map and Set objects. + */ +function applyReviver(reviver, obj, key, val) { + if (val && typeof val === 'object') { + if (Array.isArray(val)) { + for (let i = 0, len = val.length; i < len; ++i) { + const v0 = val[i]; + const v1 = applyReviver(reviver, val, String(i), v0); + // eslint-disable-next-line @typescript-eslint/no-array-delete + if (v1 === undefined) + delete val[i]; + else if (v1 !== v0) + val[i] = v1; + } + } + else if (val instanceof Map) { + for (const k of Array.from(val.keys())) { + const v0 = val.get(k); + const v1 = applyReviver(reviver, val, k, v0); + if (v1 === undefined) + val.delete(k); + else if (v1 !== v0) + val.set(k, v1); + } + } + else if (val instanceof Set) { + for (const v0 of Array.from(val)) { + const v1 = applyReviver(reviver, val, v0, v0); + if (v1 === undefined) + val.delete(v0); + else if (v1 !== v0) { + val.delete(v0); + val.add(v1); + } + } + } + else { + for (const [k, v0] of Object.entries(val)) { + const v1 = applyReviver(reviver, val, k, v0); + if (v1 === undefined) + delete val[k]; + else if (v1 !== v0) + val[k] = v1; + } + } + } + return reviver.call(obj, key, val); +} + +/** + * Recursively convert any node or its contents to native JavaScript + * + * @param value - The input value + * @param arg - If `value` defines a `toJSON()` method, use this + * as its first argument + * @param ctx - Conversion context, originally set in Document#toJS(). If + * `{ keep: true }` is not set, output should be suitable for JSON + * stringification. + */ +function toJS(value, arg, ctx) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + if (Array.isArray(value)) + return value.map((v, i) => toJS(v, String(i), ctx)); + if (value && typeof value.toJSON === 'function') { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + if (!ctx || !hasAnchor(value)) + return value.toJSON(arg, ctx); + const data = { aliasCount: 0, count: 1, res: undefined }; + ctx.anchors.set(value, data); + ctx.onCreate = res => { + data.res = res; + delete ctx.onCreate; + }; + const res = value.toJSON(arg, ctx); + if (ctx.onCreate) + ctx.onCreate(res); + return res; + } + if (typeof value === 'bigint' && !ctx?.keep) + return Number(value); + return value; +} + +class NodeBase { + constructor(type) { + Object.defineProperty(this, NODE_TYPE, { value: type }); + } + /** Create a copy of this node. */ + clone() { + const copy = Object.create(Object.getPrototypeOf(this), Object.getOwnPropertyDescriptors(this)); + if (this.range) + copy.range = this.range.slice(); + return copy; + } + /** A plain JavaScript representation of this node. */ + toJS(doc, { mapAsMap, maxAliasCount, onAnchor, reviver } = {}) { + if (!isDocument(doc)) + throw new TypeError('A document argument is required'); + const ctx = { + anchors: new Map(), + doc, + keep: true, + mapAsMap: mapAsMap === true, + mapKeyWarned: false, + maxAliasCount: typeof maxAliasCount === 'number' ? maxAliasCount : 100 + }; + const res = toJS(this, '', ctx); + if (typeof onAnchor === 'function') + for (const { count, res } of ctx.anchors.values()) + onAnchor(res, count); + return typeof reviver === 'function' + ? applyReviver(reviver, { '': res }, '', res) + : res; + } +} + +class Alias extends NodeBase { + constructor(source) { + super(ALIAS); + this.source = source; + Object.defineProperty(this, 'tag', { + set() { + throw new Error('Alias nodes cannot have tags'); + } + }); + } + /** + * Resolve the value of this alias within `doc`, finding the last + * instance of the `source` anchor before this node. + */ + resolve(doc) { + let found = undefined; + visit$1(doc, { + Node: (_key, node) => { + if (node === this) + return visit$1.BREAK; + if (node.anchor === this.source) + found = node; + } + }); + return found; + } + toJSON(_arg, ctx) { + if (!ctx) + return { source: this.source }; + const { anchors, doc, maxAliasCount } = ctx; + const source = this.resolve(doc); + if (!source) { + const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`; + throw new ReferenceError(msg); + } + let data = anchors.get(source); + if (!data) { + // Resolve anchors for Node.prototype.toJS() + toJS(source, null, ctx); + data = anchors.get(source); + } + /* istanbul ignore if */ + if (!data || data.res === undefined) { + const msg = 'This should not happen: Alias anchor was not resolved?'; + throw new ReferenceError(msg); + } + if (maxAliasCount >= 0) { + data.count += 1; + if (data.aliasCount === 0) + data.aliasCount = getAliasCount(doc, source, anchors); + if (data.count * data.aliasCount > maxAliasCount) { + const msg = 'Excessive alias count indicates a resource exhaustion attack'; + throw new ReferenceError(msg); + } + } + return data.res; + } + toString(ctx, _onComment, _onChompKeep) { + const src = `*${this.source}`; + if (ctx) { + anchorIsValid(this.source); + if (ctx.options.verifyAliasOrder && !ctx.anchors.has(this.source)) { + const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`; + throw new Error(msg); + } + if (ctx.implicitKey) + return `${src} `; + } + return src; + } +} +function getAliasCount(doc, node, anchors) { + if (isAlias(node)) { + const source = node.resolve(doc); + const anchor = anchors && source && anchors.get(source); + return anchor ? anchor.count * anchor.aliasCount : 0; + } + else if (isCollection$1(node)) { + let count = 0; + for (const item of node.items) { + const c = getAliasCount(doc, item, anchors); + if (c > count) + count = c; + } + return count; + } + else if (isPair(node)) { + const kc = getAliasCount(doc, node.key, anchors); + const vc = getAliasCount(doc, node.value, anchors); + return Math.max(kc, vc); + } + return 1; +} + +const isScalarValue = (value) => !value || (typeof value !== 'function' && typeof value !== 'object'); +class Scalar extends NodeBase { + constructor(value) { + super(SCALAR$1); + this.value = value; + } + toJSON(arg, ctx) { + return ctx?.keep ? this.value : toJS(this.value, arg, ctx); + } + toString() { + return String(this.value); + } +} +Scalar.BLOCK_FOLDED = 'BLOCK_FOLDED'; +Scalar.BLOCK_LITERAL = 'BLOCK_LITERAL'; +Scalar.PLAIN = 'PLAIN'; +Scalar.QUOTE_DOUBLE = 'QUOTE_DOUBLE'; +Scalar.QUOTE_SINGLE = 'QUOTE_SINGLE'; + +const defaultTagPrefix = 'tag:yaml.org,2002:'; +function findTagObject(value, tagName, tags) { + if (tagName) { + const match = tags.filter(t => t.tag === tagName); + const tagObj = match.find(t => !t.format) ?? match[0]; + if (!tagObj) + throw new Error(`Tag ${tagName} not found`); + return tagObj; + } + return tags.find(t => t.identify?.(value) && !t.format); +} +function createNode(value, tagName, ctx) { + if (isDocument(value)) + value = value.contents; + if (isNode(value)) + return value; + if (isPair(value)) { + const map = ctx.schema[MAP].createNode?.(ctx.schema, null, ctx); + map.items.push(value); + return map; + } + if (value instanceof String || + value instanceof Number || + value instanceof Boolean || + (typeof BigInt !== 'undefined' && value instanceof BigInt) // not supported everywhere + ) { + // https://tc39.es/ecma262/#sec-serializejsonproperty + value = value.valueOf(); + } + const { aliasDuplicateObjects, onAnchor, onTagObj, schema, sourceObjects } = ctx; + // Detect duplicate references to the same object & use Alias nodes for all + // after first. The `ref` wrapper allows for circular references to resolve. + let ref = undefined; + if (aliasDuplicateObjects && value && typeof value === 'object') { + ref = sourceObjects.get(value); + if (ref) { + if (!ref.anchor) + ref.anchor = onAnchor(value); + return new Alias(ref.anchor); + } + else { + ref = { anchor: null, node: null }; + sourceObjects.set(value, ref); + } + } + if (tagName?.startsWith('!!')) + tagName = defaultTagPrefix + tagName.slice(2); + let tagObj = findTagObject(value, tagName, schema.tags); + if (!tagObj) { + if (value && typeof value.toJSON === 'function') { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + value = value.toJSON(); + } + if (!value || typeof value !== 'object') { + const node = new Scalar(value); + if (ref) + ref.node = node; + return node; + } + tagObj = + value instanceof Map + ? schema[MAP] + : Symbol.iterator in Object(value) + ? schema[SEQ] + : schema[MAP]; + } + if (onTagObj) { + onTagObj(tagObj); + delete ctx.onTagObj; + } + const node = tagObj?.createNode + ? tagObj.createNode(ctx.schema, value, ctx) + : typeof tagObj?.nodeClass?.from === 'function' + ? tagObj.nodeClass.from(ctx.schema, value, ctx) + : new Scalar(value); + if (tagName) + node.tag = tagName; + else if (!tagObj.default) + node.tag = tagObj.tag; + if (ref) + ref.node = node; + return node; +} + +function collectionFromPath(schema, path, value) { + let v = value; + for (let i = path.length - 1; i >= 0; --i) { + const k = path[i]; + if (typeof k === 'number' && Number.isInteger(k) && k >= 0) { + const a = []; + a[k] = v; + v = a; + } + else { + v = new Map([[k, v]]); + } + } + return createNode(v, undefined, { + aliasDuplicateObjects: false, + keepUndefined: false, + onAnchor: () => { + throw new Error('This should not happen, please report a bug.'); + }, + schema, + sourceObjects: new Map() + }); +} +// Type guard is intentionally a little wrong so as to be more useful, +// as it does not cover untypable empty non-string iterables (e.g. []). +const isEmptyPath = (path) => path == null || + (typeof path === 'object' && !!path[Symbol.iterator]().next().done); +class Collection extends NodeBase { + constructor(type, schema) { + super(type); + Object.defineProperty(this, 'schema', { + value: schema, + configurable: true, + enumerable: false, + writable: true + }); + } + /** + * Create a copy of this collection. + * + * @param schema - If defined, overwrites the original's schema + */ + clone(schema) { + const copy = Object.create(Object.getPrototypeOf(this), Object.getOwnPropertyDescriptors(this)); + if (schema) + copy.schema = schema; + copy.items = copy.items.map(it => isNode(it) || isPair(it) ? it.clone(schema) : it); + if (this.range) + copy.range = this.range.slice(); + return copy; + } + /** + * Adds a value to the collection. For `!!map` and `!!omap` the value must + * be a Pair instance or a `{ key, value }` object, which may not have a key + * that already exists in the map. + */ + addIn(path, value) { + if (isEmptyPath(path)) + this.add(value); + else { + const [key, ...rest] = path; + const node = this.get(key, true); + if (isCollection$1(node)) + node.addIn(rest, value); + else if (node === undefined && this.schema) + this.set(key, collectionFromPath(this.schema, rest, value)); + else + throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`); + } + } + /** + * Removes a value from the collection. + * @returns `true` if the item was found and removed. + */ + deleteIn(path) { + const [key, ...rest] = path; + if (rest.length === 0) + return this.delete(key); + const node = this.get(key, true); + if (isCollection$1(node)) + return node.deleteIn(rest); + else + throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`); + } + /** + * Returns item at `key`, or `undefined` if not found. By default unwraps + * scalar values from their surrounding node; to disable set `keepScalar` to + * `true` (collections are always returned intact). + */ + getIn(path, keepScalar) { + const [key, ...rest] = path; + const node = this.get(key, true); + if (rest.length === 0) + return !keepScalar && isScalar$1(node) ? node.value : node; + else + return isCollection$1(node) ? node.getIn(rest, keepScalar) : undefined; + } + hasAllNullValues(allowScalar) { + return this.items.every(node => { + if (!isPair(node)) + return false; + const n = node.value; + return (n == null || + (allowScalar && + isScalar$1(n) && + n.value == null && + !n.commentBefore && + !n.comment && + !n.tag)); + }); + } + /** + * Checks if the collection includes a value with the key `key`. + */ + hasIn(path) { + const [key, ...rest] = path; + if (rest.length === 0) + return this.has(key); + const node = this.get(key, true); + return isCollection$1(node) ? node.hasIn(rest) : false; + } + /** + * Sets a value in this collection. For `!!set`, `value` needs to be a + * boolean to add/remove the item from the set. + */ + setIn(path, value) { + const [key, ...rest] = path; + if (rest.length === 0) { + this.set(key, value); + } + else { + const node = this.get(key, true); + if (isCollection$1(node)) + node.setIn(rest, value); + else if (node === undefined && this.schema) + this.set(key, collectionFromPath(this.schema, rest, value)); + else + throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`); + } + } +} + +/** + * Stringifies a comment. + * + * Empty comment lines are left empty, + * lines consisting of a single space are replaced by `#`, + * and all other lines are prefixed with a `#`. + */ +const stringifyComment = (str) => str.replace(/^(?!$)(?: $)?/gm, '#'); +function indentComment(comment, indent) { + if (/^\n+$/.test(comment)) + return comment.substring(1); + return indent ? comment.replace(/^(?! *$)/gm, indent) : comment; +} +const lineComment = (str, indent, comment) => str.endsWith('\n') + ? indentComment(comment, indent) + : comment.includes('\n') + ? '\n' + indentComment(comment, indent) + : (str.endsWith(' ') ? '' : ' ') + comment; + +const FOLD_FLOW = 'flow'; +const FOLD_BLOCK = 'block'; +const FOLD_QUOTED = 'quoted'; +/** + * Tries to keep input at up to `lineWidth` characters, splitting only on spaces + * not followed by newlines or spaces unless `mode` is `'quoted'`. Lines are + * terminated with `\n` and started with `indent`. + */ +function foldFlowLines(text, indent, mode = 'flow', { indentAtStart, lineWidth = 80, minContentWidth = 20, onFold, onOverflow } = {}) { + if (!lineWidth || lineWidth < 0) + return text; + if (lineWidth < minContentWidth) + minContentWidth = 0; + const endStep = Math.max(1 + minContentWidth, 1 + lineWidth - indent.length); + if (text.length <= endStep) + return text; + const folds = []; + const escapedFolds = {}; + let end = lineWidth - indent.length; + if (typeof indentAtStart === 'number') { + if (indentAtStart > lineWidth - Math.max(2, minContentWidth)) + folds.push(0); + else + end = lineWidth - indentAtStart; + } + let split = undefined; + let prev = undefined; + let overflow = false; + let i = -1; + let escStart = -1; + let escEnd = -1; + if (mode === FOLD_BLOCK) { + i = consumeMoreIndentedLines(text, i, indent.length); + if (i !== -1) + end = i + endStep; + } + for (let ch; (ch = text[(i += 1)]);) { + if (mode === FOLD_QUOTED && ch === '\\') { + escStart = i; + switch (text[i + 1]) { + case 'x': + i += 3; + break; + case 'u': + i += 5; + break; + case 'U': + i += 9; + break; + default: + i += 1; + } + escEnd = i; + } + if (ch === '\n') { + if (mode === FOLD_BLOCK) + i = consumeMoreIndentedLines(text, i, indent.length); + end = i + indent.length + endStep; + split = undefined; + } + else { + if (ch === ' ' && + prev && + prev !== ' ' && + prev !== '\n' && + prev !== '\t') { + // space surrounded by non-space can be replaced with newline + indent + const next = text[i + 1]; + if (next && next !== ' ' && next !== '\n' && next !== '\t') + split = i; + } + if (i >= end) { + if (split) { + folds.push(split); + end = split + endStep; + split = undefined; + } + else if (mode === FOLD_QUOTED) { + // white-space collected at end may stretch past lineWidth + while (prev === ' ' || prev === '\t') { + prev = ch; + ch = text[(i += 1)]; + overflow = true; + } + // Account for newline escape, but don't break preceding escape + const j = i > escEnd + 1 ? i - 2 : escStart - 1; + // Bail out if lineWidth & minContentWidth are shorter than an escape string + if (escapedFolds[j]) + return text; + folds.push(j); + escapedFolds[j] = true; + end = j + endStep; + split = undefined; + } + else { + overflow = true; + } + } + } + prev = ch; + } + if (overflow && onOverflow) + onOverflow(); + if (folds.length === 0) + return text; + if (onFold) + onFold(); + let res = text.slice(0, folds[0]); + for (let i = 0; i < folds.length; ++i) { + const fold = folds[i]; + const end = folds[i + 1] || text.length; + if (fold === 0) + res = `\n${indent}${text.slice(0, end)}`; + else { + if (mode === FOLD_QUOTED && escapedFolds[fold]) + res += `${text[fold]}\\`; + res += `\n${indent}${text.slice(fold + 1, end)}`; + } + } + return res; +} +/** + * Presumes `i + 1` is at the start of a line + * @returns index of last newline in more-indented block + */ +function consumeMoreIndentedLines(text, i, indent) { + let end = i; + let start = i + 1; + let ch = text[start]; + while (ch === ' ' || ch === '\t') { + if (i < start + indent) { + ch = text[++i]; + } + else { + do { + ch = text[++i]; + } while (ch && ch !== '\n'); + end = i; + start = i + 1; + ch = text[start]; + } + } + return end; +} + +const getFoldOptions = (ctx, isBlock) => ({ + indentAtStart: isBlock ? ctx.indent.length : ctx.indentAtStart, + lineWidth: ctx.options.lineWidth, + minContentWidth: ctx.options.minContentWidth +}); +// Also checks for lines starting with %, as parsing the output as YAML 1.1 will +// presume that's starting a new document. +const containsDocumentMarker = (str) => /^(%|---|\.\.\.)/m.test(str); +function lineLengthOverLimit(str, lineWidth, indentLength) { + if (!lineWidth || lineWidth < 0) + return false; + const limit = lineWidth - indentLength; + const strLen = str.length; + if (strLen <= limit) + return false; + for (let i = 0, start = 0; i < strLen; ++i) { + if (str[i] === '\n') { + if (i - start > limit) + return true; + start = i + 1; + if (strLen - start <= limit) + return false; + } + } + return true; +} +function doubleQuotedString(value, ctx) { + const json = JSON.stringify(value); + if (ctx.options.doubleQuotedAsJSON) + return json; + const { implicitKey } = ctx; + const minMultiLineLength = ctx.options.doubleQuotedMinMultiLineLength; + const indent = ctx.indent || (containsDocumentMarker(value) ? ' ' : ''); + let str = ''; + let start = 0; + for (let i = 0, ch = json[i]; ch; ch = json[++i]) { + if (ch === ' ' && json[i + 1] === '\\' && json[i + 2] === 'n') { + // space before newline needs to be escaped to not be folded + str += json.slice(start, i) + '\\ '; + i += 1; + start = i; + ch = '\\'; + } + if (ch === '\\') + switch (json[i + 1]) { + case 'u': + { + str += json.slice(start, i); + const code = json.substr(i + 2, 4); + switch (code) { + case '0000': + str += '\\0'; + break; + case '0007': + str += '\\a'; + break; + case '000b': + str += '\\v'; + break; + case '001b': + str += '\\e'; + break; + case '0085': + str += '\\N'; + break; + case '00a0': + str += '\\_'; + break; + case '2028': + str += '\\L'; + break; + case '2029': + str += '\\P'; + break; + default: + if (code.substr(0, 2) === '00') + str += '\\x' + code.substr(2); + else + str += json.substr(i, 6); + } + i += 5; + start = i + 1; + } + break; + case 'n': + if (implicitKey || + json[i + 2] === '"' || + json.length < minMultiLineLength) { + i += 1; + } + else { + // folding will eat first newline + str += json.slice(start, i) + '\n\n'; + while (json[i + 2] === '\\' && + json[i + 3] === 'n' && + json[i + 4] !== '"') { + str += '\n'; + i += 2; + } + str += indent; + // space after newline needs to be escaped to not be folded + if (json[i + 2] === ' ') + str += '\\'; + i += 1; + start = i + 1; + } + break; + default: + i += 1; + } + } + str = start ? str + json.slice(start) : json; + return implicitKey + ? str + : foldFlowLines(str, indent, FOLD_QUOTED, getFoldOptions(ctx, false)); +} +function singleQuotedString(value, ctx) { + if (ctx.options.singleQuote === false || + (ctx.implicitKey && value.includes('\n')) || + /[ \t]\n|\n[ \t]/.test(value) // single quoted string can't have leading or trailing whitespace around newline + ) + return doubleQuotedString(value, ctx); + const indent = ctx.indent || (containsDocumentMarker(value) ? ' ' : ''); + const res = "'" + value.replace(/'/g, "''").replace(/\n+/g, `$&\n${indent}`) + "'"; + return ctx.implicitKey + ? res + : foldFlowLines(res, indent, FOLD_FLOW, getFoldOptions(ctx, false)); +} +function quotedString(value, ctx) { + const { singleQuote } = ctx.options; + let qs; + if (singleQuote === false) + qs = doubleQuotedString; + else { + const hasDouble = value.includes('"'); + const hasSingle = value.includes("'"); + if (hasDouble && !hasSingle) + qs = singleQuotedString; + else if (hasSingle && !hasDouble) + qs = doubleQuotedString; + else + qs = singleQuote ? singleQuotedString : doubleQuotedString; + } + return qs(value, ctx); +} +// The negative lookbehind avoids a polynomial search, +// but isn't supported yet on Safari: https://caniuse.com/js-regexp-lookbehind +let blockEndNewlines; +try { + blockEndNewlines = new RegExp('(^|(?\n'; + // determine chomping from whitespace at value end + let chomp; + let endStart; + for (endStart = value.length; endStart > 0; --endStart) { + const ch = value[endStart - 1]; + if (ch !== '\n' && ch !== '\t' && ch !== ' ') + break; + } + let end = value.substring(endStart); + const endNlPos = end.indexOf('\n'); + if (endNlPos === -1) { + chomp = '-'; // strip + } + else if (value === end || endNlPos !== end.length - 1) { + chomp = '+'; // keep + if (onChompKeep) + onChompKeep(); + } + else { + chomp = ''; // clip + } + if (end) { + value = value.slice(0, -end.length); + if (end[end.length - 1] === '\n') + end = end.slice(0, -1); + end = end.replace(blockEndNewlines, `$&${indent}`); + } + // determine indent indicator from whitespace at value start + let startWithSpace = false; + let startEnd; + let startNlPos = -1; + for (startEnd = 0; startEnd < value.length; ++startEnd) { + const ch = value[startEnd]; + if (ch === ' ') + startWithSpace = true; + else if (ch === '\n') + startNlPos = startEnd; + else + break; + } + let start = value.substring(0, startNlPos < startEnd ? startNlPos + 1 : startEnd); + if (start) { + value = value.substring(start.length); + start = start.replace(/\n+/g, `$&${indent}`); + } + const indentSize = indent ? '2' : '1'; // root is at -1 + let header = (literal ? '|' : '>') + (startWithSpace ? indentSize : '') + chomp; + if (comment) { + header += ' ' + commentString(comment.replace(/ ?[\r\n]+/g, ' ')); + if (onComment) + onComment(); + } + if (literal) { + value = value.replace(/\n+/g, `$&${indent}`); + return `${header}\n${indent}${start}${value}${end}`; + } + value = value + .replace(/\n+/g, '\n$&') + .replace(/(?:^|\n)([\t ].*)(?:([\n\t ]*)\n(?![\n\t ]))?/g, '$1$2') // more-indented lines aren't folded + // ^ more-ind. ^ empty ^ capture next empty lines only at end of indent + .replace(/\n+/g, `$&${indent}`); + const body = foldFlowLines(`${start}${value}${end}`, indent, FOLD_BLOCK, getFoldOptions(ctx, true)); + return `${header}\n${indent}${body}`; +} +function plainString(item, ctx, onComment, onChompKeep) { + const { type, value } = item; + const { actualString, implicitKey, indent, indentStep, inFlow } = ctx; + if ((implicitKey && value.includes('\n')) || + (inFlow && /[[\]{},]/.test(value))) { + return quotedString(value, ctx); + } + if (!value || + /^[\n\t ,[\]{}#&*!|>'"%@`]|^[?-]$|^[?-][ \t]|[\n:][ \t]|[ \t]\n|[\n\t ]#|[\n\t :]$/.test(value)) { + // not allowed: + // - empty string, '-' or '?' + // - start with an indicator character (except [?:-]) or /[?-] / + // - '\n ', ': ' or ' \n' anywhere + // - '#' not preceded by a non-space char + // - end with ' ' or ':' + return implicitKey || inFlow || !value.includes('\n') + ? quotedString(value, ctx) + : blockString(item, ctx, onComment, onChompKeep); + } + if (!implicitKey && + !inFlow && + type !== Scalar.PLAIN && + value.includes('\n')) { + // Where allowed & type not set explicitly, prefer block style for multiline strings + return blockString(item, ctx, onComment, onChompKeep); + } + if (containsDocumentMarker(value)) { + if (indent === '') { + ctx.forceBlockIndent = true; + return blockString(item, ctx, onComment, onChompKeep); + } + else if (implicitKey && indent === indentStep) { + return quotedString(value, ctx); + } + } + const str = value.replace(/\n+/g, `$&\n${indent}`); + // Verify that output will be parsed as a string, as e.g. plain numbers and + // booleans get parsed with those types in v1.2 (e.g. '42', 'true' & '0.9e-3'), + // and others in v1.1. + if (actualString) { + const test = (tag) => tag.default && tag.tag !== 'tag:yaml.org,2002:str' && tag.test?.test(str); + const { compat, tags } = ctx.doc.schema; + if (tags.some(test) || compat?.some(test)) + return quotedString(value, ctx); + } + return implicitKey + ? str + : foldFlowLines(str, indent, FOLD_FLOW, getFoldOptions(ctx, false)); +} +function stringifyString(item, ctx, onComment, onChompKeep) { + const { implicitKey, inFlow } = ctx; + const ss = typeof item.value === 'string' + ? item + : Object.assign({}, item, { value: String(item.value) }); + let { type } = item; + if (type !== Scalar.QUOTE_DOUBLE) { + // force double quotes on control characters & unpaired surrogates + if (/[\x00-\x08\x0b-\x1f\x7f-\x9f\u{D800}-\u{DFFF}]/u.test(ss.value)) + type = Scalar.QUOTE_DOUBLE; + } + const _stringify = (_type) => { + switch (_type) { + case Scalar.BLOCK_FOLDED: + case Scalar.BLOCK_LITERAL: + return implicitKey || inFlow + ? quotedString(ss.value, ctx) // blocks are not valid inside flow containers + : blockString(ss, ctx, onComment, onChompKeep); + case Scalar.QUOTE_DOUBLE: + return doubleQuotedString(ss.value, ctx); + case Scalar.QUOTE_SINGLE: + return singleQuotedString(ss.value, ctx); + case Scalar.PLAIN: + return plainString(ss, ctx, onComment, onChompKeep); + default: + return null; + } + }; + let res = _stringify(type); + if (res === null) { + const { defaultKeyType, defaultStringType } = ctx.options; + const t = (implicitKey && defaultKeyType) || defaultStringType; + res = _stringify(t); + if (res === null) + throw new Error(`Unsupported default string type ${t}`); + } + return res; +} + +function createStringifyContext(doc, options) { + const opt = Object.assign({ + blockQuote: true, + commentString: stringifyComment, + defaultKeyType: null, + defaultStringType: 'PLAIN', + directives: null, + doubleQuotedAsJSON: false, + doubleQuotedMinMultiLineLength: 40, + falseStr: 'false', + flowCollectionPadding: true, + indentSeq: true, + lineWidth: 80, + minContentWidth: 20, + nullStr: 'null', + simpleKeys: false, + singleQuote: null, + trueStr: 'true', + verifyAliasOrder: true + }, doc.schema.toStringOptions, options); + let inFlow; + switch (opt.collectionStyle) { + case 'block': + inFlow = false; + break; + case 'flow': + inFlow = true; + break; + default: + inFlow = null; + } + return { + anchors: new Set(), + doc, + flowCollectionPadding: opt.flowCollectionPadding ? ' ' : '', + indent: '', + indentStep: typeof opt.indent === 'number' ? ' '.repeat(opt.indent) : ' ', + inFlow, + options: opt + }; +} +function getTagObject(tags, item) { + if (item.tag) { + const match = tags.filter(t => t.tag === item.tag); + if (match.length > 0) + return match.find(t => t.format === item.format) ?? match[0]; + } + let tagObj = undefined; + let obj; + if (isScalar$1(item)) { + obj = item.value; + const match = tags.filter(t => t.identify?.(obj)); + tagObj = + match.find(t => t.format === item.format) ?? match.find(t => !t.format); + } + else { + obj = item; + tagObj = tags.find(t => t.nodeClass && obj instanceof t.nodeClass); + } + if (!tagObj) { + const name = obj?.constructor?.name ?? typeof obj; + throw new Error(`Tag not resolved for ${name} value`); + } + return tagObj; +} +// needs to be called before value stringifier to allow for circular anchor refs +function stringifyProps(node, tagObj, { anchors, doc }) { + if (!doc.directives) + return ''; + const props = []; + const anchor = (isScalar$1(node) || isCollection$1(node)) && node.anchor; + if (anchor && anchorIsValid(anchor)) { + anchors.add(anchor); + props.push(`&${anchor}`); + } + const tag = node.tag ? node.tag : tagObj.default ? null : tagObj.tag; + if (tag) + props.push(doc.directives.tagString(tag)); + return props.join(' '); +} +function stringify$2(item, ctx, onComment, onChompKeep) { + if (isPair(item)) + return item.toString(ctx, onComment, onChompKeep); + if (isAlias(item)) { + if (ctx.doc.directives) + return item.toString(ctx); + if (ctx.resolvedAliases?.has(item)) { + throw new TypeError(`Cannot stringify circular structure without alias nodes`); + } + else { + if (ctx.resolvedAliases) + ctx.resolvedAliases.add(item); + else + ctx.resolvedAliases = new Set([item]); + item = item.resolve(ctx.doc); + } + } + let tagObj = undefined; + const node = isNode(item) + ? item + : ctx.doc.createNode(item, { onTagObj: o => (tagObj = o) }); + if (!tagObj) + tagObj = getTagObject(ctx.doc.schema.tags, node); + const props = stringifyProps(node, tagObj, ctx); + if (props.length > 0) + ctx.indentAtStart = (ctx.indentAtStart ?? 0) + props.length + 1; + const str = typeof tagObj.stringify === 'function' + ? tagObj.stringify(node, ctx, onComment, onChompKeep) + : isScalar$1(node) + ? stringifyString(node, ctx, onComment, onChompKeep) + : node.toString(ctx, onComment, onChompKeep); + if (!props) + return str; + return isScalar$1(node) || str[0] === '{' || str[0] === '[' + ? `${props} ${str}` + : `${props}\n${ctx.indent}${str}`; +} + +function stringifyPair({ key, value }, ctx, onComment, onChompKeep) { + const { allNullValues, doc, indent, indentStep, options: { commentString, indentSeq, simpleKeys } } = ctx; + let keyComment = (isNode(key) && key.comment) || null; + if (simpleKeys) { + if (keyComment) { + throw new Error('With simple keys, key nodes cannot have comments'); + } + if (isCollection$1(key) || (!isNode(key) && typeof key === 'object')) { + const msg = 'With simple keys, collection cannot be used as a key value'; + throw new Error(msg); + } + } + let explicitKey = !simpleKeys && + (!key || + (keyComment && value == null && !ctx.inFlow) || + isCollection$1(key) || + (isScalar$1(key) + ? key.type === Scalar.BLOCK_FOLDED || key.type === Scalar.BLOCK_LITERAL + : typeof key === 'object')); + ctx = Object.assign({}, ctx, { + allNullValues: false, + implicitKey: !explicitKey && (simpleKeys || !allNullValues), + indent: indent + indentStep + }); + let keyCommentDone = false; + let chompKeep = false; + let str = stringify$2(key, ctx, () => (keyCommentDone = true), () => (chompKeep = true)); + if (!explicitKey && !ctx.inFlow && str.length > 1024) { + if (simpleKeys) + throw new Error('With simple keys, single line scalar must not span more than 1024 characters'); + explicitKey = true; + } + if (ctx.inFlow) { + if (allNullValues || value == null) { + if (keyCommentDone && onComment) + onComment(); + return str === '' ? '?' : explicitKey ? `? ${str}` : str; + } + } + else if ((allNullValues && !simpleKeys) || (value == null && explicitKey)) { + str = `? ${str}`; + if (keyComment && !keyCommentDone) { + str += lineComment(str, ctx.indent, commentString(keyComment)); + } + else if (chompKeep && onChompKeep) + onChompKeep(); + return str; + } + if (keyCommentDone) + keyComment = null; + if (explicitKey) { + if (keyComment) + str += lineComment(str, ctx.indent, commentString(keyComment)); + str = `? ${str}\n${indent}:`; + } + else { + str = `${str}:`; + if (keyComment) + str += lineComment(str, ctx.indent, commentString(keyComment)); + } + let vsb, vcb, valueComment; + if (isNode(value)) { + vsb = !!value.spaceBefore; + vcb = value.commentBefore; + valueComment = value.comment; + } + else { + vsb = false; + vcb = null; + valueComment = null; + if (value && typeof value === 'object') + value = doc.createNode(value); + } + ctx.implicitKey = false; + if (!explicitKey && !keyComment && isScalar$1(value)) + ctx.indentAtStart = str.length + 1; + chompKeep = false; + if (!indentSeq && + indentStep.length >= 2 && + !ctx.inFlow && + !explicitKey && + isSeq(value) && + !value.flow && + !value.tag && + !value.anchor) { + // If indentSeq === false, consider '- ' as part of indentation where possible + ctx.indent = ctx.indent.substring(2); + } + let valueCommentDone = false; + const valueStr = stringify$2(value, ctx, () => (valueCommentDone = true), () => (chompKeep = true)); + let ws = ' '; + if (keyComment || vsb || vcb) { + ws = vsb ? '\n' : ''; + if (vcb) { + const cs = commentString(vcb); + ws += `\n${indentComment(cs, ctx.indent)}`; + } + if (valueStr === '' && !ctx.inFlow) { + if (ws === '\n') + ws = '\n\n'; + } + else { + ws += `\n${ctx.indent}`; + } + } + else if (!explicitKey && isCollection$1(value)) { + const vs0 = valueStr[0]; + const nl0 = valueStr.indexOf('\n'); + const hasNewline = nl0 !== -1; + const flow = ctx.inFlow ?? value.flow ?? value.items.length === 0; + if (hasNewline || !flow) { + let hasPropsLine = false; + if (hasNewline && (vs0 === '&' || vs0 === '!')) { + let sp0 = valueStr.indexOf(' '); + if (vs0 === '&' && + sp0 !== -1 && + sp0 < nl0 && + valueStr[sp0 + 1] === '!') { + sp0 = valueStr.indexOf(' ', sp0 + 1); + } + if (sp0 === -1 || nl0 < sp0) + hasPropsLine = true; + } + if (!hasPropsLine) + ws = `\n${ctx.indent}`; + } + } + else if (valueStr === '' || valueStr[0] === '\n') { + ws = ''; + } + str += ws + valueStr; + if (ctx.inFlow) { + if (valueCommentDone && onComment) + onComment(); + } + else if (valueComment && !valueCommentDone) { + str += lineComment(str, ctx.indent, commentString(valueComment)); + } + else if (chompKeep && onChompKeep) { + onChompKeep(); + } + return str; +} + +function warn(logLevel, warning) { + if (logLevel === 'debug' || logLevel === 'warn') { + if (typeof process !== 'undefined' && process.emitWarning) + process.emitWarning(warning); + else + console.warn(warning); + } +} + +const MERGE_KEY = '<<'; +function addPairToJSMap(ctx, map, { key, value }) { + if (ctx?.doc.schema.merge && isMergeKey(key)) { + value = isAlias(value) ? value.resolve(ctx.doc) : value; + if (isSeq(value)) + for (const it of value.items) + mergeToJSMap(ctx, map, it); + else if (Array.isArray(value)) + for (const it of value) + mergeToJSMap(ctx, map, it); + else + mergeToJSMap(ctx, map, value); + } + else { + const jsKey = toJS(key, '', ctx); + if (map instanceof Map) { + map.set(jsKey, toJS(value, jsKey, ctx)); + } + else if (map instanceof Set) { + map.add(jsKey); + } + else { + const stringKey = stringifyKey(key, jsKey, ctx); + const jsValue = toJS(value, stringKey, ctx); + if (stringKey in map) + Object.defineProperty(map, stringKey, { + value: jsValue, + writable: true, + enumerable: true, + configurable: true + }); + else + map[stringKey] = jsValue; + } + } + return map; +} +const isMergeKey = (key) => key === MERGE_KEY || + (isScalar$1(key) && + key.value === MERGE_KEY && + (!key.type || key.type === Scalar.PLAIN)); +// If the value associated with a merge key is a single mapping node, each of +// its key/value pairs is inserted into the current mapping, unless the key +// already exists in it. If the value associated with the merge key is a +// sequence, then this sequence is expected to contain mapping nodes and each +// of these nodes is merged in turn according to its order in the sequence. +// Keys in mapping nodes earlier in the sequence override keys specified in +// later mapping nodes. -- http://yaml.org/type/merge.html +function mergeToJSMap(ctx, map, value) { + const source = ctx && isAlias(value) ? value.resolve(ctx.doc) : value; + if (!isMap(source)) + throw new Error('Merge sources must be maps or map aliases'); + const srcMap = source.toJSON(null, ctx, Map); + for (const [key, value] of srcMap) { + if (map instanceof Map) { + if (!map.has(key)) + map.set(key, value); + } + else if (map instanceof Set) { + map.add(key); + } + else if (!Object.prototype.hasOwnProperty.call(map, key)) { + Object.defineProperty(map, key, { + value, + writable: true, + enumerable: true, + configurable: true + }); + } + } + return map; +} +function stringifyKey(key, jsKey, ctx) { + if (jsKey === null) + return ''; + if (typeof jsKey !== 'object') + return String(jsKey); + if (isNode(key) && ctx?.doc) { + const strCtx = createStringifyContext(ctx.doc, {}); + strCtx.anchors = new Set(); + for (const node of ctx.anchors.keys()) + strCtx.anchors.add(node.anchor); + strCtx.inFlow = true; + strCtx.inStringifyKey = true; + const strKey = key.toString(strCtx); + if (!ctx.mapKeyWarned) { + let jsonStr = JSON.stringify(strKey); + if (jsonStr.length > 40) + jsonStr = jsonStr.substring(0, 36) + '..."'; + warn(ctx.doc.options.logLevel, `Keys with collection values will be stringified due to JS Object restrictions: ${jsonStr}. Set mapAsMap: true to use object keys.`); + ctx.mapKeyWarned = true; + } + return strKey; + } + return JSON.stringify(jsKey); +} + +function createPair(key, value, ctx) { + const k = createNode(key, undefined, ctx); + const v = createNode(value, undefined, ctx); + return new Pair(k, v); +} +class Pair { + constructor(key, value = null) { + Object.defineProperty(this, NODE_TYPE, { value: PAIR }); + this.key = key; + this.value = value; + } + clone(schema) { + let { key, value } = this; + if (isNode(key)) + key = key.clone(schema); + if (isNode(value)) + value = value.clone(schema); + return new Pair(key, value); + } + toJSON(_, ctx) { + const pair = ctx?.mapAsMap ? new Map() : {}; + return addPairToJSMap(ctx, pair, this); + } + toString(ctx, onComment, onChompKeep) { + return ctx?.doc + ? stringifyPair(this, ctx, onComment, onChompKeep) + : JSON.stringify(this); + } +} + +function stringifyCollection(collection, ctx, options) { + const flow = ctx.inFlow ?? collection.flow; + const stringify = flow ? stringifyFlowCollection : stringifyBlockCollection; + return stringify(collection, ctx, options); +} +function stringifyBlockCollection({ comment, items }, ctx, { blockItemPrefix, flowChars, itemIndent, onChompKeep, onComment }) { + const { indent, options: { commentString } } = ctx; + const itemCtx = Object.assign({}, ctx, { indent: itemIndent, type: null }); + let chompKeep = false; // flag for the preceding node's status + const lines = []; + for (let i = 0; i < items.length; ++i) { + const item = items[i]; + let comment = null; + if (isNode(item)) { + if (!chompKeep && item.spaceBefore) + lines.push(''); + addCommentBefore(ctx, lines, item.commentBefore, chompKeep); + if (item.comment) + comment = item.comment; + } + else if (isPair(item)) { + const ik = isNode(item.key) ? item.key : null; + if (ik) { + if (!chompKeep && ik.spaceBefore) + lines.push(''); + addCommentBefore(ctx, lines, ik.commentBefore, chompKeep); + } + } + chompKeep = false; + let str = stringify$2(item, itemCtx, () => (comment = null), () => (chompKeep = true)); + if (comment) + str += lineComment(str, itemIndent, commentString(comment)); + if (chompKeep && comment) + chompKeep = false; + lines.push(blockItemPrefix + str); + } + let str; + if (lines.length === 0) { + str = flowChars.start + flowChars.end; + } + else { + str = lines[0]; + for (let i = 1; i < lines.length; ++i) { + const line = lines[i]; + str += line ? `\n${indent}${line}` : '\n'; + } + } + if (comment) { + str += '\n' + indentComment(commentString(comment), indent); + if (onComment) + onComment(); + } + else if (chompKeep && onChompKeep) + onChompKeep(); + return str; +} +function stringifyFlowCollection({ items }, ctx, { flowChars, itemIndent }) { + const { indent, indentStep, flowCollectionPadding: fcPadding, options: { commentString } } = ctx; + itemIndent += indentStep; + const itemCtx = Object.assign({}, ctx, { + indent: itemIndent, + inFlow: true, + type: null + }); + let reqNewline = false; + let linesAtValue = 0; + const lines = []; + for (let i = 0; i < items.length; ++i) { + const item = items[i]; + let comment = null; + if (isNode(item)) { + if (item.spaceBefore) + lines.push(''); + addCommentBefore(ctx, lines, item.commentBefore, false); + if (item.comment) + comment = item.comment; + } + else if (isPair(item)) { + const ik = isNode(item.key) ? item.key : null; + if (ik) { + if (ik.spaceBefore) + lines.push(''); + addCommentBefore(ctx, lines, ik.commentBefore, false); + if (ik.comment) + reqNewline = true; + } + const iv = isNode(item.value) ? item.value : null; + if (iv) { + if (iv.comment) + comment = iv.comment; + if (iv.commentBefore) + reqNewline = true; + } + else if (item.value == null && ik?.comment) { + comment = ik.comment; + } + } + if (comment) + reqNewline = true; + let str = stringify$2(item, itemCtx, () => (comment = null)); + if (i < items.length - 1) + str += ','; + if (comment) + str += lineComment(str, itemIndent, commentString(comment)); + if (!reqNewline && (lines.length > linesAtValue || str.includes('\n'))) + reqNewline = true; + lines.push(str); + linesAtValue = lines.length; + } + const { start, end } = flowChars; + if (lines.length === 0) { + return start + end; + } + else { + if (!reqNewline) { + const len = lines.reduce((sum, line) => sum + line.length + 2, 2); + reqNewline = ctx.options.lineWidth > 0 && len > ctx.options.lineWidth; + } + if (reqNewline) { + let str = start; + for (const line of lines) + str += line ? `\n${indentStep}${indent}${line}` : '\n'; + return `${str}\n${indent}${end}`; + } + else { + return `${start}${fcPadding}${lines.join(' ')}${fcPadding}${end}`; + } + } +} +function addCommentBefore({ indent, options: { commentString } }, lines, comment, chompKeep) { + if (comment && chompKeep) + comment = comment.replace(/^\n+/, ''); + if (comment) { + const ic = indentComment(commentString(comment), indent); + lines.push(ic.trimStart()); // Avoid double indent on first line + } +} + +function findPair(items, key) { + const k = isScalar$1(key) ? key.value : key; + for (const it of items) { + if (isPair(it)) { + if (it.key === key || it.key === k) + return it; + if (isScalar$1(it.key) && it.key.value === k) + return it; + } + } + return undefined; +} +class YAMLMap extends Collection { + static get tagName() { + return 'tag:yaml.org,2002:map'; + } + constructor(schema) { + super(MAP, schema); + this.items = []; + } + /** + * A generic collection parsing method that can be extended + * to other node classes that inherit from YAMLMap + */ + static from(schema, obj, ctx) { + const { keepUndefined, replacer } = ctx; + const map = new this(schema); + const add = (key, value) => { + if (typeof replacer === 'function') + value = replacer.call(obj, key, value); + else if (Array.isArray(replacer) && !replacer.includes(key)) + return; + if (value !== undefined || keepUndefined) + map.items.push(createPair(key, value, ctx)); + }; + if (obj instanceof Map) { + for (const [key, value] of obj) + add(key, value); + } + else if (obj && typeof obj === 'object') { + for (const key of Object.keys(obj)) + add(key, obj[key]); + } + if (typeof schema.sortMapEntries === 'function') { + map.items.sort(schema.sortMapEntries); + } + return map; + } + /** + * Adds a value to the collection. + * + * @param overwrite - If not set `true`, using a key that is already in the + * collection will throw. Otherwise, overwrites the previous value. + */ + add(pair, overwrite) { + let _pair; + if (isPair(pair)) + _pair = pair; + else if (!pair || typeof pair !== 'object' || !('key' in pair)) { + // In TypeScript, this never happens. + _pair = new Pair(pair, pair?.value); + } + else + _pair = new Pair(pair.key, pair.value); + const prev = findPair(this.items, _pair.key); + const sortEntries = this.schema?.sortMapEntries; + if (prev) { + if (!overwrite) + throw new Error(`Key ${_pair.key} already set`); + // For scalars, keep the old node & its comments and anchors + if (isScalar$1(prev.value) && isScalarValue(_pair.value)) + prev.value.value = _pair.value; + else + prev.value = _pair.value; + } + else if (sortEntries) { + const i = this.items.findIndex(item => sortEntries(_pair, item) < 0); + if (i === -1) + this.items.push(_pair); + else + this.items.splice(i, 0, _pair); + } + else { + this.items.push(_pair); + } + } + delete(key) { + const it = findPair(this.items, key); + if (!it) + return false; + const del = this.items.splice(this.items.indexOf(it), 1); + return del.length > 0; + } + get(key, keepScalar) { + const it = findPair(this.items, key); + const node = it?.value; + return (!keepScalar && isScalar$1(node) ? node.value : node) ?? undefined; + } + has(key) { + return !!findPair(this.items, key); + } + set(key, value) { + this.add(new Pair(key, value), true); + } + /** + * @param ctx - Conversion context, originally set in Document#toJS() + * @param {Class} Type - If set, forces the returned collection type + * @returns Instance of Type, Map, or Object + */ + toJSON(_, ctx, Type) { + const map = Type ? new Type() : ctx?.mapAsMap ? new Map() : {}; + if (ctx?.onCreate) + ctx.onCreate(map); + for (const item of this.items) + addPairToJSMap(ctx, map, item); + return map; + } + toString(ctx, onComment, onChompKeep) { + if (!ctx) + return JSON.stringify(this); + for (const item of this.items) { + if (!isPair(item)) + throw new Error(`Map items must all be pairs; found ${JSON.stringify(item)} instead`); + } + if (!ctx.allNullValues && this.hasAllNullValues(false)) + ctx = Object.assign({}, ctx, { allNullValues: true }); + return stringifyCollection(this, ctx, { + blockItemPrefix: '', + flowChars: { start: '{', end: '}' }, + itemIndent: ctx.indent || '', + onChompKeep, + onComment + }); + } +} + +const map = { + collection: 'map', + default: true, + nodeClass: YAMLMap, + tag: 'tag:yaml.org,2002:map', + resolve(map, onError) { + if (!isMap(map)) + onError('Expected a mapping for this tag'); + return map; + }, + createNode: (schema, obj, ctx) => YAMLMap.from(schema, obj, ctx) +}; + +class YAMLSeq extends Collection { + static get tagName() { + return 'tag:yaml.org,2002:seq'; + } + constructor(schema) { + super(SEQ, schema); + this.items = []; + } + add(value) { + this.items.push(value); + } + /** + * Removes a value from the collection. + * + * `key` must contain a representation of an integer for this to succeed. + * It may be wrapped in a `Scalar`. + * + * @returns `true` if the item was found and removed. + */ + delete(key) { + const idx = asItemIndex(key); + if (typeof idx !== 'number') + return false; + const del = this.items.splice(idx, 1); + return del.length > 0; + } + get(key, keepScalar) { + const idx = asItemIndex(key); + if (typeof idx !== 'number') + return undefined; + const it = this.items[idx]; + return !keepScalar && isScalar$1(it) ? it.value : it; + } + /** + * Checks if the collection includes a value with the key `key`. + * + * `key` must contain a representation of an integer for this to succeed. + * It may be wrapped in a `Scalar`. + */ + has(key) { + const idx = asItemIndex(key); + return typeof idx === 'number' && idx < this.items.length; + } + /** + * Sets a value in this collection. For `!!set`, `value` needs to be a + * boolean to add/remove the item from the set. + * + * If `key` does not contain a representation of an integer, this will throw. + * It may be wrapped in a `Scalar`. + */ + set(key, value) { + const idx = asItemIndex(key); + if (typeof idx !== 'number') + throw new Error(`Expected a valid index, not ${key}.`); + const prev = this.items[idx]; + if (isScalar$1(prev) && isScalarValue(value)) + prev.value = value; + else + this.items[idx] = value; + } + toJSON(_, ctx) { + const seq = []; + if (ctx?.onCreate) + ctx.onCreate(seq); + let i = 0; + for (const item of this.items) + seq.push(toJS(item, String(i++), ctx)); + return seq; + } + toString(ctx, onComment, onChompKeep) { + if (!ctx) + return JSON.stringify(this); + return stringifyCollection(this, ctx, { + blockItemPrefix: '- ', + flowChars: { start: '[', end: ']' }, + itemIndent: (ctx.indent || '') + ' ', + onChompKeep, + onComment + }); + } + static from(schema, obj, ctx) { + const { replacer } = ctx; + const seq = new this(schema); + if (obj && Symbol.iterator in Object(obj)) { + let i = 0; + for (let it of obj) { + if (typeof replacer === 'function') { + const key = obj instanceof Set ? it : String(i++); + it = replacer.call(obj, key, it); + } + seq.items.push(createNode(it, undefined, ctx)); + } + } + return seq; + } +} +function asItemIndex(key) { + let idx = isScalar$1(key) ? key.value : key; + if (idx && typeof idx === 'string') + idx = Number(idx); + return typeof idx === 'number' && Number.isInteger(idx) && idx >= 0 + ? idx + : null; +} + +const seq = { + collection: 'seq', + default: true, + nodeClass: YAMLSeq, + tag: 'tag:yaml.org,2002:seq', + resolve(seq, onError) { + if (!isSeq(seq)) + onError('Expected a sequence for this tag'); + return seq; + }, + createNode: (schema, obj, ctx) => YAMLSeq.from(schema, obj, ctx) +}; + +const string = { + identify: value => typeof value === 'string', + default: true, + tag: 'tag:yaml.org,2002:str', + resolve: str => str, + stringify(item, ctx, onComment, onChompKeep) { + ctx = Object.assign({ actualString: true }, ctx); + return stringifyString(item, ctx, onComment, onChompKeep); + } +}; + +const nullTag = { + identify: value => value == null, + createNode: () => new Scalar(null), + default: true, + tag: 'tag:yaml.org,2002:null', + test: /^(?:~|[Nn]ull|NULL)?$/, + resolve: () => new Scalar(null), + stringify: ({ source }, ctx) => typeof source === 'string' && nullTag.test.test(source) + ? source + : ctx.options.nullStr +}; + +const boolTag = { + identify: value => typeof value === 'boolean', + default: true, + tag: 'tag:yaml.org,2002:bool', + test: /^(?:[Tt]rue|TRUE|[Ff]alse|FALSE)$/, + resolve: str => new Scalar(str[0] === 't' || str[0] === 'T'), + stringify({ source, value }, ctx) { + if (source && boolTag.test.test(source)) { + const sv = source[0] === 't' || source[0] === 'T'; + if (value === sv) + return source; + } + return value ? ctx.options.trueStr : ctx.options.falseStr; + } +}; + +function stringifyNumber({ format, minFractionDigits, tag, value }) { + if (typeof value === 'bigint') + return String(value); + const num = typeof value === 'number' ? value : Number(value); + if (!isFinite(num)) + return isNaN(num) ? '.nan' : num < 0 ? '-.inf' : '.inf'; + let n = JSON.stringify(value); + if (!format && + minFractionDigits && + (!tag || tag === 'tag:yaml.org,2002:float') && + /^\d/.test(n)) { + let i = n.indexOf('.'); + if (i < 0) { + i = n.length; + n += '.'; + } + let d = minFractionDigits - (n.length - i - 1); + while (d-- > 0) + n += '0'; + } + return n; +} + +const floatNaN$1 = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + test: /^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/, + resolve: str => str.slice(-3).toLowerCase() === 'nan' + ? NaN + : str[0] === '-' + ? Number.NEGATIVE_INFINITY + : Number.POSITIVE_INFINITY, + stringify: stringifyNumber +}; +const floatExp$1 = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + format: 'EXP', + test: /^[-+]?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)[eE][-+]?[0-9]+$/, + resolve: str => parseFloat(str), + stringify(node) { + const num = Number(node.value); + return isFinite(num) ? num.toExponential() : stringifyNumber(node); + } +}; +const float$1 = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + test: /^[-+]?(?:\.[0-9]+|[0-9]+\.[0-9]*)$/, + resolve(str) { + const node = new Scalar(parseFloat(str)); + const dot = str.indexOf('.'); + if (dot !== -1 && str[str.length - 1] === '0') + node.minFractionDigits = str.length - dot - 1; + return node; + }, + stringify: stringifyNumber +}; + +const intIdentify$2 = (value) => typeof value === 'bigint' || Number.isInteger(value); +const intResolve$1 = (str, offset, radix, { intAsBigInt }) => (intAsBigInt ? BigInt(str) : parseInt(str.substring(offset), radix)); +function intStringify$1(node, radix, prefix) { + const { value } = node; + if (intIdentify$2(value) && value >= 0) + return prefix + value.toString(radix); + return stringifyNumber(node); +} +const intOct$1 = { + identify: value => intIdentify$2(value) && value >= 0, + default: true, + tag: 'tag:yaml.org,2002:int', + format: 'OCT', + test: /^0o[0-7]+$/, + resolve: (str, _onError, opt) => intResolve$1(str, 2, 8, opt), + stringify: node => intStringify$1(node, 8, '0o') +}; +const int$1 = { + identify: intIdentify$2, + default: true, + tag: 'tag:yaml.org,2002:int', + test: /^[-+]?[0-9]+$/, + resolve: (str, _onError, opt) => intResolve$1(str, 0, 10, opt), + stringify: stringifyNumber +}; +const intHex$1 = { + identify: value => intIdentify$2(value) && value >= 0, + default: true, + tag: 'tag:yaml.org,2002:int', + format: 'HEX', + test: /^0x[0-9a-fA-F]+$/, + resolve: (str, _onError, opt) => intResolve$1(str, 2, 16, opt), + stringify: node => intStringify$1(node, 16, '0x') +}; + +const schema$2 = [ + map, + seq, + string, + nullTag, + boolTag, + intOct$1, + int$1, + intHex$1, + floatNaN$1, + floatExp$1, + float$1 +]; + +function intIdentify$1(value) { + return typeof value === 'bigint' || Number.isInteger(value); +} +const stringifyJSON = ({ value }) => JSON.stringify(value); +const jsonScalars = [ + { + identify: value => typeof value === 'string', + default: true, + tag: 'tag:yaml.org,2002:str', + resolve: str => str, + stringify: stringifyJSON + }, + { + identify: value => value == null, + createNode: () => new Scalar(null), + default: true, + tag: 'tag:yaml.org,2002:null', + test: /^null$/, + resolve: () => null, + stringify: stringifyJSON + }, + { + identify: value => typeof value === 'boolean', + default: true, + tag: 'tag:yaml.org,2002:bool', + test: /^true|false$/, + resolve: str => str === 'true', + stringify: stringifyJSON + }, + { + identify: intIdentify$1, + default: true, + tag: 'tag:yaml.org,2002:int', + test: /^-?(?:0|[1-9][0-9]*)$/, + resolve: (str, _onError, { intAsBigInt }) => intAsBigInt ? BigInt(str) : parseInt(str, 10), + stringify: ({ value }) => intIdentify$1(value) ? value.toString() : JSON.stringify(value) + }, + { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + test: /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]*)?(?:[eE][-+]?[0-9]+)?$/, + resolve: str => parseFloat(str), + stringify: stringifyJSON + } +]; +const jsonError = { + default: true, + tag: '', + test: /^/, + resolve(str, onError) { + onError(`Unresolved plain scalar ${JSON.stringify(str)}`); + return str; + } +}; +const schema$1 = [map, seq].concat(jsonScalars, jsonError); + +const binary = { + identify: value => value instanceof Uint8Array, // Buffer inherits from Uint8Array + default: false, + tag: 'tag:yaml.org,2002:binary', + /** + * Returns a Buffer in node and an Uint8Array in browsers + * + * To use the resulting buffer as an image, you'll want to do something like: + * + * const blob = new Blob([buffer], { type: 'image/jpeg' }) + * document.querySelector('#photo').src = URL.createObjectURL(blob) + */ + resolve(src, onError) { + if (typeof Buffer === 'function') { + return Buffer.from(src, 'base64'); + } + else if (typeof atob === 'function') { + // On IE 11, atob() can't handle newlines + const str = atob(src.replace(/[\n\r]/g, '')); + const buffer = new Uint8Array(str.length); + for (let i = 0; i < str.length; ++i) + buffer[i] = str.charCodeAt(i); + return buffer; + } + else { + onError('This environment does not support reading binary tags; either Buffer or atob is required'); + return src; + } + }, + stringify({ comment, type, value }, ctx, onComment, onChompKeep) { + const buf = value; // checked earlier by binary.identify() + let str; + if (typeof Buffer === 'function') { + str = + buf instanceof Buffer + ? buf.toString('base64') + : Buffer.from(buf.buffer).toString('base64'); + } + else if (typeof btoa === 'function') { + let s = ''; + for (let i = 0; i < buf.length; ++i) + s += String.fromCharCode(buf[i]); + str = btoa(s); + } + else { + throw new Error('This environment does not support writing binary tags; either Buffer or btoa is required'); + } + if (!type) + type = Scalar.BLOCK_LITERAL; + if (type !== Scalar.QUOTE_DOUBLE) { + const lineWidth = Math.max(ctx.options.lineWidth - ctx.indent.length, ctx.options.minContentWidth); + const n = Math.ceil(str.length / lineWidth); + const lines = new Array(n); + for (let i = 0, o = 0; i < n; ++i, o += lineWidth) { + lines[i] = str.substr(o, lineWidth); + } + str = lines.join(type === Scalar.BLOCK_LITERAL ? '\n' : ' '); + } + return stringifyString({ comment, type, value: str }, ctx, onComment, onChompKeep); + } +}; + +function resolvePairs(seq, onError) { + if (isSeq(seq)) { + for (let i = 0; i < seq.items.length; ++i) { + let item = seq.items[i]; + if (isPair(item)) + continue; + else if (isMap(item)) { + if (item.items.length > 1) + onError('Each pair must have its own sequence indicator'); + const pair = item.items[0] || new Pair(new Scalar(null)); + if (item.commentBefore) + pair.key.commentBefore = pair.key.commentBefore + ? `${item.commentBefore}\n${pair.key.commentBefore}` + : item.commentBefore; + if (item.comment) { + const cn = pair.value ?? pair.key; + cn.comment = cn.comment + ? `${item.comment}\n${cn.comment}` + : item.comment; + } + item = pair; + } + seq.items[i] = isPair(item) ? item : new Pair(item); + } + } + else + onError('Expected a sequence for this tag'); + return seq; +} +function createPairs(schema, iterable, ctx) { + const { replacer } = ctx; + const pairs = new YAMLSeq(schema); + pairs.tag = 'tag:yaml.org,2002:pairs'; + let i = 0; + if (iterable && Symbol.iterator in Object(iterable)) + for (let it of iterable) { + if (typeof replacer === 'function') + it = replacer.call(iterable, String(i++), it); + let key, value; + if (Array.isArray(it)) { + if (it.length === 2) { + key = it[0]; + value = it[1]; + } + else + throw new TypeError(`Expected [key, value] tuple: ${it}`); + } + else if (it && it instanceof Object) { + const keys = Object.keys(it); + if (keys.length === 1) { + key = keys[0]; + value = it[key]; + } + else { + throw new TypeError(`Expected tuple with one key, not ${keys.length} keys`); + } + } + else { + key = it; + } + pairs.items.push(createPair(key, value, ctx)); + } + return pairs; +} +const pairs = { + collection: 'seq', + default: false, + tag: 'tag:yaml.org,2002:pairs', + resolve: resolvePairs, + createNode: createPairs +}; + +class YAMLOMap extends YAMLSeq { + constructor() { + super(); + this.add = YAMLMap.prototype.add.bind(this); + this.delete = YAMLMap.prototype.delete.bind(this); + this.get = YAMLMap.prototype.get.bind(this); + this.has = YAMLMap.prototype.has.bind(this); + this.set = YAMLMap.prototype.set.bind(this); + this.tag = YAMLOMap.tag; + } + /** + * If `ctx` is given, the return type is actually `Map`, + * but TypeScript won't allow widening the signature of a child method. + */ + toJSON(_, ctx) { + if (!ctx) + return super.toJSON(_); + const map = new Map(); + if (ctx?.onCreate) + ctx.onCreate(map); + for (const pair of this.items) { + let key, value; + if (isPair(pair)) { + key = toJS(pair.key, '', ctx); + value = toJS(pair.value, key, ctx); + } + else { + key = toJS(pair, '', ctx); + } + if (map.has(key)) + throw new Error('Ordered maps must not include duplicate keys'); + map.set(key, value); + } + return map; + } + static from(schema, iterable, ctx) { + const pairs = createPairs(schema, iterable, ctx); + const omap = new this(); + omap.items = pairs.items; + return omap; + } +} +YAMLOMap.tag = 'tag:yaml.org,2002:omap'; +const omap = { + collection: 'seq', + identify: value => value instanceof Map, + nodeClass: YAMLOMap, + default: false, + tag: 'tag:yaml.org,2002:omap', + resolve(seq, onError) { + const pairs = resolvePairs(seq, onError); + const seenKeys = []; + for (const { key } of pairs.items) { + if (isScalar$1(key)) { + if (seenKeys.includes(key.value)) { + onError(`Ordered maps must not include duplicate keys: ${key.value}`); + } + else { + seenKeys.push(key.value); + } + } + } + return Object.assign(new YAMLOMap(), pairs); + }, + createNode: (schema, iterable, ctx) => YAMLOMap.from(schema, iterable, ctx) +}; + +function boolStringify({ value, source }, ctx) { + const boolObj = value ? trueTag : falseTag; + if (source && boolObj.test.test(source)) + return source; + return value ? ctx.options.trueStr : ctx.options.falseStr; +} +const trueTag = { + identify: value => value === true, + default: true, + tag: 'tag:yaml.org,2002:bool', + test: /^(?:Y|y|[Yy]es|YES|[Tt]rue|TRUE|[Oo]n|ON)$/, + resolve: () => new Scalar(true), + stringify: boolStringify +}; +const falseTag = { + identify: value => value === false, + default: true, + tag: 'tag:yaml.org,2002:bool', + test: /^(?:N|n|[Nn]o|NO|[Ff]alse|FALSE|[Oo]ff|OFF)$/, + resolve: () => new Scalar(false), + stringify: boolStringify +}; + +const floatNaN = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + test: /^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/, + resolve: (str) => str.slice(-3).toLowerCase() === 'nan' + ? NaN + : str[0] === '-' + ? Number.NEGATIVE_INFINITY + : Number.POSITIVE_INFINITY, + stringify: stringifyNumber +}; +const floatExp = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + format: 'EXP', + test: /^[-+]?(?:[0-9][0-9_]*)?(?:\.[0-9_]*)?[eE][-+]?[0-9]+$/, + resolve: (str) => parseFloat(str.replace(/_/g, '')), + stringify(node) { + const num = Number(node.value); + return isFinite(num) ? num.toExponential() : stringifyNumber(node); + } +}; +const float = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + test: /^[-+]?(?:[0-9][0-9_]*)?\.[0-9_]*$/, + resolve(str) { + const node = new Scalar(parseFloat(str.replace(/_/g, ''))); + const dot = str.indexOf('.'); + if (dot !== -1) { + const f = str.substring(dot + 1).replace(/_/g, ''); + if (f[f.length - 1] === '0') + node.minFractionDigits = f.length; + } + return node; + }, + stringify: stringifyNumber +}; + +const intIdentify = (value) => typeof value === 'bigint' || Number.isInteger(value); +function intResolve(str, offset, radix, { intAsBigInt }) { + const sign = str[0]; + if (sign === '-' || sign === '+') + offset += 1; + str = str.substring(offset).replace(/_/g, ''); + if (intAsBigInt) { + switch (radix) { + case 2: + str = `0b${str}`; + break; + case 8: + str = `0o${str}`; + break; + case 16: + str = `0x${str}`; + break; + } + const n = BigInt(str); + return sign === '-' ? BigInt(-1) * n : n; + } + const n = parseInt(str, radix); + return sign === '-' ? -1 * n : n; +} +function intStringify(node, radix, prefix) { + const { value } = node; + if (intIdentify(value)) { + const str = value.toString(radix); + return value < 0 ? '-' + prefix + str.substr(1) : prefix + str; + } + return stringifyNumber(node); +} +const intBin = { + identify: intIdentify, + default: true, + tag: 'tag:yaml.org,2002:int', + format: 'BIN', + test: /^[-+]?0b[0-1_]+$/, + resolve: (str, _onError, opt) => intResolve(str, 2, 2, opt), + stringify: node => intStringify(node, 2, '0b') +}; +const intOct = { + identify: intIdentify, + default: true, + tag: 'tag:yaml.org,2002:int', + format: 'OCT', + test: /^[-+]?0[0-7_]+$/, + resolve: (str, _onError, opt) => intResolve(str, 1, 8, opt), + stringify: node => intStringify(node, 8, '0') +}; +const int = { + identify: intIdentify, + default: true, + tag: 'tag:yaml.org,2002:int', + test: /^[-+]?[0-9][0-9_]*$/, + resolve: (str, _onError, opt) => intResolve(str, 0, 10, opt), + stringify: stringifyNumber +}; +const intHex = { + identify: intIdentify, + default: true, + tag: 'tag:yaml.org,2002:int', + format: 'HEX', + test: /^[-+]?0x[0-9a-fA-F_]+$/, + resolve: (str, _onError, opt) => intResolve(str, 2, 16, opt), + stringify: node => intStringify(node, 16, '0x') +}; + +class YAMLSet extends YAMLMap { + constructor(schema) { + super(schema); + this.tag = YAMLSet.tag; + } + add(key) { + let pair; + if (isPair(key)) + pair = key; + else if (key && + typeof key === 'object' && + 'key' in key && + 'value' in key && + key.value === null) + pair = new Pair(key.key, null); + else + pair = new Pair(key, null); + const prev = findPair(this.items, pair.key); + if (!prev) + this.items.push(pair); + } + /** + * If `keepPair` is `true`, returns the Pair matching `key`. + * Otherwise, returns the value of that Pair's key. + */ + get(key, keepPair) { + const pair = findPair(this.items, key); + return !keepPair && isPair(pair) + ? isScalar$1(pair.key) + ? pair.key.value + : pair.key + : pair; + } + set(key, value) { + if (typeof value !== 'boolean') + throw new Error(`Expected boolean value for set(key, value) in a YAML set, not ${typeof value}`); + const prev = findPair(this.items, key); + if (prev && !value) { + this.items.splice(this.items.indexOf(prev), 1); + } + else if (!prev && value) { + this.items.push(new Pair(key)); + } + } + toJSON(_, ctx) { + return super.toJSON(_, ctx, Set); + } + toString(ctx, onComment, onChompKeep) { + if (!ctx) + return JSON.stringify(this); + if (this.hasAllNullValues(true)) + return super.toString(Object.assign({}, ctx, { allNullValues: true }), onComment, onChompKeep); + else + throw new Error('Set items must all have null values'); + } + static from(schema, iterable, ctx) { + const { replacer } = ctx; + const set = new this(schema); + if (iterable && Symbol.iterator in Object(iterable)) + for (let value of iterable) { + if (typeof replacer === 'function') + value = replacer.call(iterable, value, value); + set.items.push(createPair(value, null, ctx)); + } + return set; + } +} +YAMLSet.tag = 'tag:yaml.org,2002:set'; +const set = { + collection: 'map', + identify: value => value instanceof Set, + nodeClass: YAMLSet, + default: false, + tag: 'tag:yaml.org,2002:set', + createNode: (schema, iterable, ctx) => YAMLSet.from(schema, iterable, ctx), + resolve(map, onError) { + if (isMap(map)) { + if (map.hasAllNullValues(true)) + return Object.assign(new YAMLSet(), map); + else + onError('Set items must all have null values'); + } + else + onError('Expected a mapping for this tag'); + return map; + } +}; + +/** Internal types handle bigint as number, because TS can't figure it out. */ +function parseSexagesimal(str, asBigInt) { + const sign = str[0]; + const parts = sign === '-' || sign === '+' ? str.substring(1) : str; + const num = (n) => asBigInt ? BigInt(n) : Number(n); + const res = parts + .replace(/_/g, '') + .split(':') + .reduce((res, p) => res * num(60) + num(p), num(0)); + return (sign === '-' ? num(-1) * res : res); +} +/** + * hhhh:mm:ss.sss + * + * Internal types handle bigint as number, because TS can't figure it out. + */ +function stringifySexagesimal(node) { + let { value } = node; + let num = (n) => n; + if (typeof value === 'bigint') + num = n => BigInt(n); + else if (isNaN(value) || !isFinite(value)) + return stringifyNumber(node); + let sign = ''; + if (value < 0) { + sign = '-'; + value *= num(-1); + } + const _60 = num(60); + const parts = [value % _60]; // seconds, including ms + if (value < 60) { + parts.unshift(0); // at least one : is required + } + else { + value = (value - parts[0]) / _60; + parts.unshift(value % _60); // minutes + if (value >= 60) { + value = (value - parts[0]) / _60; + parts.unshift(value); // hours + } + } + return (sign + + parts + .map(n => String(n).padStart(2, '0')) + .join(':') + .replace(/000000\d*$/, '') // % 60 may introduce error + ); +} +const intTime = { + identify: value => typeof value === 'bigint' || Number.isInteger(value), + default: true, + tag: 'tag:yaml.org,2002:int', + format: 'TIME', + test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+$/, + resolve: (str, _onError, { intAsBigInt }) => parseSexagesimal(str, intAsBigInt), + stringify: stringifySexagesimal +}; +const floatTime = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + format: 'TIME', + test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*$/, + resolve: str => parseSexagesimal(str, false), + stringify: stringifySexagesimal +}; +const timestamp = { + identify: value => value instanceof Date, + default: true, + tag: 'tag:yaml.org,2002:timestamp', + // If the time zone is omitted, the timestamp is assumed to be specified in UTC. The time part + // may be omitted altogether, resulting in a date format. In such a case, the time part is + // assumed to be 00:00:00Z (start of day, UTC). + test: RegExp('^([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})' + // YYYY-Mm-Dd + '(?:' + // time is optional + '(?:t|T|[ \\t]+)' + // t | T | whitespace + '([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}(\\.[0-9]+)?)' + // Hh:Mm:Ss(.ss)? + '(?:[ \\t]*(Z|[-+][012]?[0-9](?::[0-9]{2})?))?' + // Z | +5 | -03:30 + ')?$'), + resolve(str) { + const match = str.match(timestamp.test); + if (!match) + throw new Error('!!timestamp expects a date, starting with yyyy-mm-dd'); + const [, year, month, day, hour, minute, second] = match.map(Number); + const millisec = match[7] ? Number((match[7] + '00').substr(1, 3)) : 0; + let date = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec); + const tz = match[8]; + if (tz && tz !== 'Z') { + let d = parseSexagesimal(tz, false); + if (Math.abs(d) < 30) + d *= 60; + date -= 60000 * d; + } + return new Date(date); + }, + stringify: ({ value }) => value.toISOString().replace(/((T00:00)?:00)?\.000Z$/, '') +}; + +const schema = [ + map, + seq, + string, + nullTag, + trueTag, + falseTag, + intBin, + intOct, + int, + intHex, + floatNaN, + floatExp, + float, + binary, + omap, + pairs, + set, + intTime, + floatTime, + timestamp +]; + +const schemas = new Map([ + ['core', schema$2], + ['failsafe', [map, seq, string]], + ['json', schema$1], + ['yaml11', schema], + ['yaml-1.1', schema] +]); +const tagsByName = { + binary, + bool: boolTag, + float: float$1, + floatExp: floatExp$1, + floatNaN: floatNaN$1, + floatTime, + int: int$1, + intHex: intHex$1, + intOct: intOct$1, + intTime, + map, + null: nullTag, + omap, + pairs, + seq, + set, + timestamp +}; +const coreKnownTags = { + 'tag:yaml.org,2002:binary': binary, + 'tag:yaml.org,2002:omap': omap, + 'tag:yaml.org,2002:pairs': pairs, + 'tag:yaml.org,2002:set': set, + 'tag:yaml.org,2002:timestamp': timestamp +}; +function getTags(customTags, schemaName) { + let tags = schemas.get(schemaName); + if (!tags) { + if (Array.isArray(customTags)) + tags = []; + else { + const keys = Array.from(schemas.keys()) + .filter(key => key !== 'yaml11') + .map(key => JSON.stringify(key)) + .join(', '); + throw new Error(`Unknown schema "${schemaName}"; use one of ${keys} or define customTags array`); + } + } + if (Array.isArray(customTags)) { + for (const tag of customTags) + tags = tags.concat(tag); + } + else if (typeof customTags === 'function') { + tags = customTags(tags.slice()); + } + return tags.map(tag => { + if (typeof tag !== 'string') + return tag; + const tagObj = tagsByName[tag]; + if (tagObj) + return tagObj; + const keys = Object.keys(tagsByName) + .map(key => JSON.stringify(key)) + .join(', '); + throw new Error(`Unknown custom tag "${tag}"; use one of ${keys}`); + }); +} + +const sortMapEntriesByKey = (a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0; +class Schema { + constructor({ compat, customTags, merge, resolveKnownTags, schema, sortMapEntries, toStringDefaults }) { + this.compat = Array.isArray(compat) + ? getTags(compat, 'compat') + : compat + ? getTags(null, compat) + : null; + this.merge = !!merge; + this.name = (typeof schema === 'string' && schema) || 'core'; + this.knownTags = resolveKnownTags ? coreKnownTags : {}; + this.tags = getTags(customTags, this.name); + this.toStringOptions = toStringDefaults ?? null; + Object.defineProperty(this, MAP, { value: map }); + Object.defineProperty(this, SCALAR$1, { value: string }); + Object.defineProperty(this, SEQ, { value: seq }); + // Used by createMap() + this.sortMapEntries = + typeof sortMapEntries === 'function' + ? sortMapEntries + : sortMapEntries === true + ? sortMapEntriesByKey + : null; + } + clone() { + const copy = Object.create(Schema.prototype, Object.getOwnPropertyDescriptors(this)); + copy.tags = this.tags.slice(); + return copy; + } +} + +function stringifyDocument(doc, options) { + const lines = []; + let hasDirectives = options.directives === true; + if (options.directives !== false && doc.directives) { + const dir = doc.directives.toString(doc); + if (dir) { + lines.push(dir); + hasDirectives = true; + } + else if (doc.directives.docStart) + hasDirectives = true; + } + if (hasDirectives) + lines.push('---'); + const ctx = createStringifyContext(doc, options); + const { commentString } = ctx.options; + if (doc.commentBefore) { + if (lines.length !== 1) + lines.unshift(''); + const cs = commentString(doc.commentBefore); + lines.unshift(indentComment(cs, '')); + } + let chompKeep = false; + let contentComment = null; + if (doc.contents) { + if (isNode(doc.contents)) { + if (doc.contents.spaceBefore && hasDirectives) + lines.push(''); + if (doc.contents.commentBefore) { + const cs = commentString(doc.contents.commentBefore); + lines.push(indentComment(cs, '')); + } + // top-level block scalars need to be indented if followed by a comment + ctx.forceBlockIndent = !!doc.comment; + contentComment = doc.contents.comment; + } + const onChompKeep = contentComment ? undefined : () => (chompKeep = true); + let body = stringify$2(doc.contents, ctx, () => (contentComment = null), onChompKeep); + if (contentComment) + body += lineComment(body, '', commentString(contentComment)); + if ((body[0] === '|' || body[0] === '>') && + lines[lines.length - 1] === '---') { + // Top-level block scalars with a preceding doc marker ought to use the + // same line for their header. + lines[lines.length - 1] = `--- ${body}`; + } + else + lines.push(body); + } + else { + lines.push(stringify$2(doc.contents, ctx)); + } + if (doc.directives?.docEnd) { + if (doc.comment) { + const cs = commentString(doc.comment); + if (cs.includes('\n')) { + lines.push('...'); + lines.push(indentComment(cs, '')); + } + else { + lines.push(`... ${cs}`); + } + } + else { + lines.push('...'); + } + } + else { + let dc = doc.comment; + if (dc && chompKeep) + dc = dc.replace(/^\n+/, ''); + if (dc) { + if ((!chompKeep || contentComment) && lines[lines.length - 1] !== '') + lines.push(''); + lines.push(indentComment(commentString(dc), '')); + } + } + return lines.join('\n') + '\n'; +} + +class Document { + constructor(value, replacer, options) { + /** A comment before this Document */ + this.commentBefore = null; + /** A comment immediately after this Document */ + this.comment = null; + /** Errors encountered during parsing. */ + this.errors = []; + /** Warnings encountered during parsing. */ + this.warnings = []; + Object.defineProperty(this, NODE_TYPE, { value: DOC }); + let _replacer = null; + if (typeof replacer === 'function' || Array.isArray(replacer)) { + _replacer = replacer; + } + else if (options === undefined && replacer) { + options = replacer; + replacer = undefined; + } + const opt = Object.assign({ + intAsBigInt: false, + keepSourceTokens: false, + logLevel: 'warn', + prettyErrors: true, + strict: true, + uniqueKeys: true, + version: '1.2' + }, options); + this.options = opt; + let { version } = opt; + if (options?._directives) { + this.directives = options._directives.atDocument(); + if (this.directives.yaml.explicit) + version = this.directives.yaml.version; + } + else + this.directives = new Directives({ version }); + this.setSchema(version, options); + // @ts-expect-error We can't really know that this matches Contents. + this.contents = + value === undefined ? null : this.createNode(value, _replacer, options); + } + /** + * Create a deep copy of this Document and its contents. + * + * Custom Node values that inherit from `Object` still refer to their original instances. + */ + clone() { + const copy = Object.create(Document.prototype, { + [NODE_TYPE]: { value: DOC } + }); + copy.commentBefore = this.commentBefore; + copy.comment = this.comment; + copy.errors = this.errors.slice(); + copy.warnings = this.warnings.slice(); + copy.options = Object.assign({}, this.options); + if (this.directives) + copy.directives = this.directives.clone(); + copy.schema = this.schema.clone(); + // @ts-expect-error We can't really know that this matches Contents. + copy.contents = isNode(this.contents) + ? this.contents.clone(copy.schema) + : this.contents; + if (this.range) + copy.range = this.range.slice(); + return copy; + } + /** Adds a value to the document. */ + add(value) { + if (assertCollection(this.contents)) + this.contents.add(value); + } + /** Adds a value to the document. */ + addIn(path, value) { + if (assertCollection(this.contents)) + this.contents.addIn(path, value); + } + /** + * Create a new `Alias` node, ensuring that the target `node` has the required anchor. + * + * If `node` already has an anchor, `name` is ignored. + * Otherwise, the `node.anchor` value will be set to `name`, + * or if an anchor with that name is already present in the document, + * `name` will be used as a prefix for a new unique anchor. + * If `name` is undefined, the generated anchor will use 'a' as a prefix. + */ + createAlias(node, name) { + if (!node.anchor) { + const prev = anchorNames(this); + node.anchor = + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + !name || prev.has(name) ? findNewAnchor(name || 'a', prev) : name; + } + return new Alias(node.anchor); + } + createNode(value, replacer, options) { + let _replacer = undefined; + if (typeof replacer === 'function') { + value = replacer.call({ '': value }, '', value); + _replacer = replacer; + } + else if (Array.isArray(replacer)) { + const keyToStr = (v) => typeof v === 'number' || v instanceof String || v instanceof Number; + const asStr = replacer.filter(keyToStr).map(String); + if (asStr.length > 0) + replacer = replacer.concat(asStr); + _replacer = replacer; + } + else if (options === undefined && replacer) { + options = replacer; + replacer = undefined; + } + const { aliasDuplicateObjects, anchorPrefix, flow, keepUndefined, onTagObj, tag } = options ?? {}; + const { onAnchor, setAnchors, sourceObjects } = createNodeAnchors(this, + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + anchorPrefix || 'a'); + const ctx = { + aliasDuplicateObjects: aliasDuplicateObjects ?? true, + keepUndefined: keepUndefined ?? false, + onAnchor, + onTagObj, + replacer: _replacer, + schema: this.schema, + sourceObjects + }; + const node = createNode(value, tag, ctx); + if (flow && isCollection$1(node)) + node.flow = true; + setAnchors(); + return node; + } + /** + * Convert a key and a value into a `Pair` using the current schema, + * recursively wrapping all values as `Scalar` or `Collection` nodes. + */ + createPair(key, value, options = {}) { + const k = this.createNode(key, null, options); + const v = this.createNode(value, null, options); + return new Pair(k, v); + } + /** + * Removes a value from the document. + * @returns `true` if the item was found and removed. + */ + delete(key) { + return assertCollection(this.contents) ? this.contents.delete(key) : false; + } + /** + * Removes a value from the document. + * @returns `true` if the item was found and removed. + */ + deleteIn(path) { + if (isEmptyPath(path)) { + if (this.contents == null) + return false; + // @ts-expect-error Presumed impossible if Strict extends false + this.contents = null; + return true; + } + return assertCollection(this.contents) + ? this.contents.deleteIn(path) + : false; + } + /** + * Returns item at `key`, or `undefined` if not found. By default unwraps + * scalar values from their surrounding node; to disable set `keepScalar` to + * `true` (collections are always returned intact). + */ + get(key, keepScalar) { + return isCollection$1(this.contents) + ? this.contents.get(key, keepScalar) + : undefined; + } + /** + * Returns item at `path`, or `undefined` if not found. By default unwraps + * scalar values from their surrounding node; to disable set `keepScalar` to + * `true` (collections are always returned intact). + */ + getIn(path, keepScalar) { + if (isEmptyPath(path)) + return !keepScalar && isScalar$1(this.contents) + ? this.contents.value + : this.contents; + return isCollection$1(this.contents) + ? this.contents.getIn(path, keepScalar) + : undefined; + } + /** + * Checks if the document includes a value with the key `key`. + */ + has(key) { + return isCollection$1(this.contents) ? this.contents.has(key) : false; + } + /** + * Checks if the document includes a value at `path`. + */ + hasIn(path) { + if (isEmptyPath(path)) + return this.contents !== undefined; + return isCollection$1(this.contents) ? this.contents.hasIn(path) : false; + } + /** + * Sets a value in this document. For `!!set`, `value` needs to be a + * boolean to add/remove the item from the set. + */ + set(key, value) { + if (this.contents == null) { + // @ts-expect-error We can't really know that this matches Contents. + this.contents = collectionFromPath(this.schema, [key], value); + } + else if (assertCollection(this.contents)) { + this.contents.set(key, value); + } + } + /** + * Sets a value in this document. For `!!set`, `value` needs to be a + * boolean to add/remove the item from the set. + */ + setIn(path, value) { + if (isEmptyPath(path)) { + // @ts-expect-error We can't really know that this matches Contents. + this.contents = value; + } + else if (this.contents == null) { + // @ts-expect-error We can't really know that this matches Contents. + this.contents = collectionFromPath(this.schema, Array.from(path), value); + } + else if (assertCollection(this.contents)) { + this.contents.setIn(path, value); + } + } + /** + * Change the YAML version and schema used by the document. + * A `null` version disables support for directives, explicit tags, anchors, and aliases. + * It also requires the `schema` option to be given as a `Schema` instance value. + * + * Overrides all previously set schema options. + */ + setSchema(version, options = {}) { + if (typeof version === 'number') + version = String(version); + let opt; + switch (version) { + case '1.1': + if (this.directives) + this.directives.yaml.version = '1.1'; + else + this.directives = new Directives({ version: '1.1' }); + opt = { merge: true, resolveKnownTags: false, schema: 'yaml-1.1' }; + break; + case '1.2': + case 'next': + if (this.directives) + this.directives.yaml.version = version; + else + this.directives = new Directives({ version }); + opt = { merge: false, resolveKnownTags: true, schema: 'core' }; + break; + case null: + if (this.directives) + delete this.directives; + opt = null; + break; + default: { + const sv = JSON.stringify(version); + throw new Error(`Expected '1.1', '1.2' or null as first argument, but found: ${sv}`); + } + } + // Not using `instanceof Schema` to allow for duck typing + if (options.schema instanceof Object) + this.schema = options.schema; + else if (opt) + this.schema = new Schema(Object.assign(opt, options)); + else + throw new Error(`With a null YAML version, the { schema: Schema } option is required`); + } + // json & jsonArg are only used from toJSON() + toJS({ json, jsonArg, mapAsMap, maxAliasCount, onAnchor, reviver } = {}) { + const ctx = { + anchors: new Map(), + doc: this, + keep: !json, + mapAsMap: mapAsMap === true, + mapKeyWarned: false, + maxAliasCount: typeof maxAliasCount === 'number' ? maxAliasCount : 100 + }; + const res = toJS(this.contents, jsonArg ?? '', ctx); + if (typeof onAnchor === 'function') + for (const { count, res } of ctx.anchors.values()) + onAnchor(res, count); + return typeof reviver === 'function' + ? applyReviver(reviver, { '': res }, '', res) + : res; + } + /** + * A JSON representation of the document `contents`. + * + * @param jsonArg Used by `JSON.stringify` to indicate the array index or + * property name. + */ + toJSON(jsonArg, onAnchor) { + return this.toJS({ json: true, jsonArg, mapAsMap: false, onAnchor }); + } + /** A YAML representation of the document. */ + toString(options = {}) { + if (this.errors.length > 0) + throw new Error('Document with errors cannot be stringified'); + if ('indent' in options && + (!Number.isInteger(options.indent) || Number(options.indent) <= 0)) { + const s = JSON.stringify(options.indent); + throw new Error(`"indent" option must be a positive integer, not ${s}`); + } + return stringifyDocument(this, options); + } +} +function assertCollection(contents) { + if (isCollection$1(contents)) + return true; + throw new Error('Expected a YAML collection as document contents'); +} + +class YAMLError extends Error { + constructor(name, pos, code, message) { + super(); + this.name = name; + this.code = code; + this.message = message; + this.pos = pos; + } +} +class YAMLParseError extends YAMLError { + constructor(pos, code, message) { + super('YAMLParseError', pos, code, message); + } +} +class YAMLWarning extends YAMLError { + constructor(pos, code, message) { + super('YAMLWarning', pos, code, message); + } +} +const prettifyError = (src, lc) => (error) => { + if (error.pos[0] === -1) + return; + error.linePos = error.pos.map(pos => lc.linePos(pos)); + const { line, col } = error.linePos[0]; + error.message += ` at line ${line}, column ${col}`; + let ci = col - 1; + let lineStr = src + .substring(lc.lineStarts[line - 1], lc.lineStarts[line]) + .replace(/[\n\r]+$/, ''); + // Trim to max 80 chars, keeping col position near the middle + if (ci >= 60 && lineStr.length > 80) { + const trimStart = Math.min(ci - 39, lineStr.length - 79); + lineStr = '…' + lineStr.substring(trimStart); + ci -= trimStart - 1; + } + if (lineStr.length > 80) + lineStr = lineStr.substring(0, 79) + '…'; + // Include previous line in context if pointing at line start + if (line > 1 && /^ *$/.test(lineStr.substring(0, ci))) { + // Regexp won't match if start is trimmed + let prev = src.substring(lc.lineStarts[line - 2], lc.lineStarts[line - 1]); + if (prev.length > 80) + prev = prev.substring(0, 79) + '…\n'; + lineStr = prev + lineStr; + } + if (/[^ ]/.test(lineStr)) { + let count = 1; + const end = error.linePos[1]; + if (end && end.line === line && end.col > col) { + count = Math.max(1, Math.min(end.col - col, 80 - ci)); + } + const pointer = ' '.repeat(ci) + '^'.repeat(count); + error.message += `:\n\n${lineStr}\n${pointer}\n`; + } +}; + +function resolveProps(tokens, { flow, indicator, next, offset, onError, parentIndent, startOnNewline }) { + let spaceBefore = false; + let atNewline = startOnNewline; + let hasSpace = startOnNewline; + let comment = ''; + let commentSep = ''; + let hasNewline = false; + let reqSpace = false; + let tab = null; + let anchor = null; + let tag = null; + let newlineAfterProp = null; + let comma = null; + let found = null; + let start = null; + for (const token of tokens) { + if (reqSpace) { + if (token.type !== 'space' && + token.type !== 'newline' && + token.type !== 'comma') + onError(token.offset, 'MISSING_CHAR', 'Tags and anchors must be separated from the next token by white space'); + reqSpace = false; + } + if (tab) { + if (atNewline && token.type !== 'comment' && token.type !== 'newline') { + onError(tab, 'TAB_AS_INDENT', 'Tabs are not allowed as indentation'); + } + tab = null; + } + switch (token.type) { + case 'space': + // At the doc level, tabs at line start may be parsed + // as leading white space rather than indentation. + // In a flow collection, only the parser handles indent. + if (!flow && + (indicator !== 'doc-start' || next?.type !== 'flow-collection') && + token.source.includes('\t')) { + tab = token; + } + hasSpace = true; + break; + case 'comment': { + if (!hasSpace) + onError(token, 'MISSING_CHAR', 'Comments must be separated from other tokens by white space characters'); + const cb = token.source.substring(1) || ' '; + if (!comment) + comment = cb; + else + comment += commentSep + cb; + commentSep = ''; + atNewline = false; + break; + } + case 'newline': + if (atNewline) { + if (comment) + comment += token.source; + else + spaceBefore = true; + } + else + commentSep += token.source; + atNewline = true; + hasNewline = true; + if (anchor || tag) + newlineAfterProp = token; + hasSpace = true; + break; + case 'anchor': + if (anchor) + onError(token, 'MULTIPLE_ANCHORS', 'A node can have at most one anchor'); + if (token.source.endsWith(':')) + onError(token.offset + token.source.length - 1, 'BAD_ALIAS', 'Anchor ending in : is ambiguous', true); + anchor = token; + if (start === null) + start = token.offset; + atNewline = false; + hasSpace = false; + reqSpace = true; + break; + case 'tag': { + if (tag) + onError(token, 'MULTIPLE_TAGS', 'A node can have at most one tag'); + tag = token; + if (start === null) + start = token.offset; + atNewline = false; + hasSpace = false; + reqSpace = true; + break; + } + case indicator: + // Could here handle preceding comments differently + if (anchor || tag) + onError(token, 'BAD_PROP_ORDER', `Anchors and tags must be after the ${token.source} indicator`); + if (found) + onError(token, 'UNEXPECTED_TOKEN', `Unexpected ${token.source} in ${flow ?? 'collection'}`); + found = token; + atNewline = + indicator === 'seq-item-ind' || indicator === 'explicit-key-ind'; + hasSpace = false; + break; + case 'comma': + if (flow) { + if (comma) + onError(token, 'UNEXPECTED_TOKEN', `Unexpected , in ${flow}`); + comma = token; + atNewline = false; + hasSpace = false; + break; + } + // else fallthrough + default: + onError(token, 'UNEXPECTED_TOKEN', `Unexpected ${token.type} token`); + atNewline = false; + hasSpace = false; + } + } + const last = tokens[tokens.length - 1]; + const end = last ? last.offset + last.source.length : offset; + if (reqSpace && + next && + next.type !== 'space' && + next.type !== 'newline' && + next.type !== 'comma' && + (next.type !== 'scalar' || next.source !== '')) { + onError(next.offset, 'MISSING_CHAR', 'Tags and anchors must be separated from the next token by white space'); + } + if (tab && + ((atNewline && tab.indent <= parentIndent) || + next?.type === 'block-map' || + next?.type === 'block-seq')) + onError(tab, 'TAB_AS_INDENT', 'Tabs are not allowed as indentation'); + return { + comma, + found, + spaceBefore, + comment, + hasNewline, + anchor, + tag, + newlineAfterProp, + end, + start: start ?? end + }; +} + +function containsNewline(key) { + if (!key) + return null; + switch (key.type) { + case 'alias': + case 'scalar': + case 'double-quoted-scalar': + case 'single-quoted-scalar': + if (key.source.includes('\n')) + return true; + if (key.end) + for (const st of key.end) + if (st.type === 'newline') + return true; + return false; + case 'flow-collection': + for (const it of key.items) { + for (const st of it.start) + if (st.type === 'newline') + return true; + if (it.sep) + for (const st of it.sep) + if (st.type === 'newline') + return true; + if (containsNewline(it.key) || containsNewline(it.value)) + return true; + } + return false; + default: + return true; + } +} + +function flowIndentCheck(indent, fc, onError) { + if (fc?.type === 'flow-collection') { + const end = fc.end[0]; + if (end.indent === indent && + (end.source === ']' || end.source === '}') && + containsNewline(fc)) { + const msg = 'Flow end indicator should be more indented than parent'; + onError(end, 'BAD_INDENT', msg, true); + } + } +} + +function mapIncludes(ctx, items, search) { + const { uniqueKeys } = ctx.options; + if (uniqueKeys === false) + return false; + const isEqual = typeof uniqueKeys === 'function' + ? uniqueKeys + : (a, b) => a === b || + (isScalar$1(a) && + isScalar$1(b) && + a.value === b.value && + !(a.value === '<<' && ctx.schema.merge)); + return items.some(pair => isEqual(pair.key, search)); +} + +const startColMsg = 'All mapping items must start at the same column'; +function resolveBlockMap({ composeNode, composeEmptyNode }, ctx, bm, onError, tag) { + const NodeClass = tag?.nodeClass ?? YAMLMap; + const map = new NodeClass(ctx.schema); + if (ctx.atRoot) + ctx.atRoot = false; + let offset = bm.offset; + let commentEnd = null; + for (const collItem of bm.items) { + const { start, key, sep, value } = collItem; + // key properties + const keyProps = resolveProps(start, { + indicator: 'explicit-key-ind', + next: key ?? sep?.[0], + offset, + onError, + parentIndent: bm.indent, + startOnNewline: true + }); + const implicitKey = !keyProps.found; + if (implicitKey) { + if (key) { + if (key.type === 'block-seq') + onError(offset, 'BLOCK_AS_IMPLICIT_KEY', 'A block sequence may not be used as an implicit map key'); + else if ('indent' in key && key.indent !== bm.indent) + onError(offset, 'BAD_INDENT', startColMsg); + } + if (!keyProps.anchor && !keyProps.tag && !sep) { + commentEnd = keyProps.end; + if (keyProps.comment) { + if (map.comment) + map.comment += '\n' + keyProps.comment; + else + map.comment = keyProps.comment; + } + continue; + } + if (keyProps.newlineAfterProp || containsNewline(key)) { + onError(key ?? start[start.length - 1], 'MULTILINE_IMPLICIT_KEY', 'Implicit keys need to be on a single line'); + } + } + else if (keyProps.found?.indent !== bm.indent) { + onError(offset, 'BAD_INDENT', startColMsg); + } + // key value + const keyStart = keyProps.end; + const keyNode = key + ? composeNode(ctx, key, keyProps, onError) + : composeEmptyNode(ctx, keyStart, start, null, keyProps, onError); + if (ctx.schema.compat) + flowIndentCheck(bm.indent, key, onError); + if (mapIncludes(ctx, map.items, keyNode)) + onError(keyStart, 'DUPLICATE_KEY', 'Map keys must be unique'); + // value properties + const valueProps = resolveProps(sep ?? [], { + indicator: 'map-value-ind', + next: value, + offset: keyNode.range[2], + onError, + parentIndent: bm.indent, + startOnNewline: !key || key.type === 'block-scalar' + }); + offset = valueProps.end; + if (valueProps.found) { + if (implicitKey) { + if (value?.type === 'block-map' && !valueProps.hasNewline) + onError(offset, 'BLOCK_AS_IMPLICIT_KEY', 'Nested mappings are not allowed in compact mappings'); + if (ctx.options.strict && + keyProps.start < valueProps.found.offset - 1024) + onError(keyNode.range, 'KEY_OVER_1024_CHARS', 'The : indicator must be at most 1024 chars after the start of an implicit block mapping key'); + } + // value value + const valueNode = value + ? composeNode(ctx, value, valueProps, onError) + : composeEmptyNode(ctx, offset, sep, null, valueProps, onError); + if (ctx.schema.compat) + flowIndentCheck(bm.indent, value, onError); + offset = valueNode.range[2]; + const pair = new Pair(keyNode, valueNode); + if (ctx.options.keepSourceTokens) + pair.srcToken = collItem; + map.items.push(pair); + } + else { + // key with no value + if (implicitKey) + onError(keyNode.range, 'MISSING_CHAR', 'Implicit map keys need to be followed by map values'); + if (valueProps.comment) { + if (keyNode.comment) + keyNode.comment += '\n' + valueProps.comment; + else + keyNode.comment = valueProps.comment; + } + const pair = new Pair(keyNode); + if (ctx.options.keepSourceTokens) + pair.srcToken = collItem; + map.items.push(pair); + } + } + if (commentEnd && commentEnd < offset) + onError(commentEnd, 'IMPOSSIBLE', 'Map comment with trailing content'); + map.range = [bm.offset, offset, commentEnd ?? offset]; + return map; +} + +function resolveBlockSeq({ composeNode, composeEmptyNode }, ctx, bs, onError, tag) { + const NodeClass = tag?.nodeClass ?? YAMLSeq; + const seq = new NodeClass(ctx.schema); + if (ctx.atRoot) + ctx.atRoot = false; + let offset = bs.offset; + let commentEnd = null; + for (const { start, value } of bs.items) { + const props = resolveProps(start, { + indicator: 'seq-item-ind', + next: value, + offset, + onError, + parentIndent: bs.indent, + startOnNewline: true + }); + if (!props.found) { + if (props.anchor || props.tag || value) { + if (value && value.type === 'block-seq') + onError(props.end, 'BAD_INDENT', 'All sequence items must start at the same column'); + else + onError(offset, 'MISSING_CHAR', 'Sequence item without - indicator'); + } + else { + commentEnd = props.end; + if (props.comment) + seq.comment = props.comment; + continue; + } + } + const node = value + ? composeNode(ctx, value, props, onError) + : composeEmptyNode(ctx, props.end, start, null, props, onError); + if (ctx.schema.compat) + flowIndentCheck(bs.indent, value, onError); + offset = node.range[2]; + seq.items.push(node); + } + seq.range = [bs.offset, offset, commentEnd ?? offset]; + return seq; +} + +function resolveEnd(end, offset, reqSpace, onError) { + let comment = ''; + if (end) { + let hasSpace = false; + let sep = ''; + for (const token of end) { + const { source, type } = token; + switch (type) { + case 'space': + hasSpace = true; + break; + case 'comment': { + if (reqSpace && !hasSpace) + onError(token, 'MISSING_CHAR', 'Comments must be separated from other tokens by white space characters'); + const cb = source.substring(1) || ' '; + if (!comment) + comment = cb; + else + comment += sep + cb; + sep = ''; + break; + } + case 'newline': + if (comment) + sep += source; + hasSpace = true; + break; + default: + onError(token, 'UNEXPECTED_TOKEN', `Unexpected ${type} at node end`); + } + offset += source.length; + } + } + return { comment, offset }; +} + +const blockMsg = 'Block collections are not allowed within flow collections'; +const isBlock = (token) => token && (token.type === 'block-map' || token.type === 'block-seq'); +function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onError, tag) { + const isMap = fc.start.source === '{'; + const fcName = isMap ? 'flow map' : 'flow sequence'; + const NodeClass = (tag?.nodeClass ?? (isMap ? YAMLMap : YAMLSeq)); + const coll = new NodeClass(ctx.schema); + coll.flow = true; + const atRoot = ctx.atRoot; + if (atRoot) + ctx.atRoot = false; + let offset = fc.offset + fc.start.source.length; + for (let i = 0; i < fc.items.length; ++i) { + const collItem = fc.items[i]; + const { start, key, sep, value } = collItem; + const props = resolveProps(start, { + flow: fcName, + indicator: 'explicit-key-ind', + next: key ?? sep?.[0], + offset, + onError, + parentIndent: fc.indent, + startOnNewline: false + }); + if (!props.found) { + if (!props.anchor && !props.tag && !sep && !value) { + if (i === 0 && props.comma) + onError(props.comma, 'UNEXPECTED_TOKEN', `Unexpected , in ${fcName}`); + else if (i < fc.items.length - 1) + onError(props.start, 'UNEXPECTED_TOKEN', `Unexpected empty item in ${fcName}`); + if (props.comment) { + if (coll.comment) + coll.comment += '\n' + props.comment; + else + coll.comment = props.comment; + } + offset = props.end; + continue; + } + if (!isMap && ctx.options.strict && containsNewline(key)) + onError(key, // checked by containsNewline() + 'MULTILINE_IMPLICIT_KEY', 'Implicit keys of flow sequence pairs need to be on a single line'); + } + if (i === 0) { + if (props.comma) + onError(props.comma, 'UNEXPECTED_TOKEN', `Unexpected , in ${fcName}`); + } + else { + if (!props.comma) + onError(props.start, 'MISSING_CHAR', `Missing , between ${fcName} items`); + if (props.comment) { + let prevItemComment = ''; + loop: for (const st of start) { + switch (st.type) { + case 'comma': + case 'space': + break; + case 'comment': + prevItemComment = st.source.substring(1); + break loop; + default: + break loop; + } + } + if (prevItemComment) { + let prev = coll.items[coll.items.length - 1]; + if (isPair(prev)) + prev = prev.value ?? prev.key; + if (prev.comment) + prev.comment += '\n' + prevItemComment; + else + prev.comment = prevItemComment; + props.comment = props.comment.substring(prevItemComment.length + 1); + } + } + } + if (!isMap && !sep && !props.found) { + // item is a value in a seq + // → key & sep are empty, start does not include ? or : + const valueNode = value + ? composeNode(ctx, value, props, onError) + : composeEmptyNode(ctx, props.end, sep, null, props, onError); + coll.items.push(valueNode); + offset = valueNode.range[2]; + if (isBlock(value)) + onError(valueNode.range, 'BLOCK_IN_FLOW', blockMsg); + } + else { + // item is a key+value pair + // key value + const keyStart = props.end; + const keyNode = key + ? composeNode(ctx, key, props, onError) + : composeEmptyNode(ctx, keyStart, start, null, props, onError); + if (isBlock(key)) + onError(keyNode.range, 'BLOCK_IN_FLOW', blockMsg); + // value properties + const valueProps = resolveProps(sep ?? [], { + flow: fcName, + indicator: 'map-value-ind', + next: value, + offset: keyNode.range[2], + onError, + parentIndent: fc.indent, + startOnNewline: false + }); + if (valueProps.found) { + if (!isMap && !props.found && ctx.options.strict) { + if (sep) + for (const st of sep) { + if (st === valueProps.found) + break; + if (st.type === 'newline') { + onError(st, 'MULTILINE_IMPLICIT_KEY', 'Implicit keys of flow sequence pairs need to be on a single line'); + break; + } + } + if (props.start < valueProps.found.offset - 1024) + onError(valueProps.found, 'KEY_OVER_1024_CHARS', 'The : indicator must be at most 1024 chars after the start of an implicit flow sequence key'); + } + } + else if (value) { + if ('source' in value && value.source && value.source[0] === ':') + onError(value, 'MISSING_CHAR', `Missing space after : in ${fcName}`); + else + onError(valueProps.start, 'MISSING_CHAR', `Missing , or : between ${fcName} items`); + } + // value value + const valueNode = value + ? composeNode(ctx, value, valueProps, onError) + : valueProps.found + ? composeEmptyNode(ctx, valueProps.end, sep, null, valueProps, onError) + : null; + if (valueNode) { + if (isBlock(value)) + onError(valueNode.range, 'BLOCK_IN_FLOW', blockMsg); + } + else if (valueProps.comment) { + if (keyNode.comment) + keyNode.comment += '\n' + valueProps.comment; + else + keyNode.comment = valueProps.comment; + } + const pair = new Pair(keyNode, valueNode); + if (ctx.options.keepSourceTokens) + pair.srcToken = collItem; + if (isMap) { + const map = coll; + if (mapIncludes(ctx, map.items, keyNode)) + onError(keyStart, 'DUPLICATE_KEY', 'Map keys must be unique'); + map.items.push(pair); + } + else { + const map = new YAMLMap(ctx.schema); + map.flow = true; + map.items.push(pair); + const endRange = (valueNode ?? keyNode).range; + map.range = [keyNode.range[0], endRange[1], endRange[2]]; + coll.items.push(map); + } + offset = valueNode ? valueNode.range[2] : valueProps.end; + } + } + const expectedEnd = isMap ? '}' : ']'; + const [ce, ...ee] = fc.end; + let cePos = offset; + if (ce && ce.source === expectedEnd) + cePos = ce.offset + ce.source.length; + else { + const name = fcName[0].toUpperCase() + fcName.substring(1); + const msg = atRoot + ? `${name} must end with a ${expectedEnd}` + : `${name} in block collection must be sufficiently indented and end with a ${expectedEnd}`; + onError(offset, atRoot ? 'MISSING_CHAR' : 'BAD_INDENT', msg); + if (ce && ce.source.length !== 1) + ee.unshift(ce); + } + if (ee.length > 0) { + const end = resolveEnd(ee, cePos, ctx.options.strict, onError); + if (end.comment) { + if (coll.comment) + coll.comment += '\n' + end.comment; + else + coll.comment = end.comment; + } + coll.range = [fc.offset, cePos, end.offset]; + } + else { + coll.range = [fc.offset, cePos, cePos]; + } + return coll; +} + +function resolveCollection(CN, ctx, token, onError, tagName, tag) { + const coll = token.type === 'block-map' + ? resolveBlockMap(CN, ctx, token, onError, tag) + : token.type === 'block-seq' + ? resolveBlockSeq(CN, ctx, token, onError, tag) + : resolveFlowCollection(CN, ctx, token, onError, tag); + const Coll = coll.constructor; + // If we got a tagName matching the class, or the tag name is '!', + // then use the tagName from the node class used to create it. + if (tagName === '!' || tagName === Coll.tagName) { + coll.tag = Coll.tagName; + return coll; + } + if (tagName) + coll.tag = tagName; + return coll; +} +function composeCollection(CN, ctx, token, props, onError) { + const tagToken = props.tag; + const tagName = !tagToken + ? null + : ctx.directives.tagName(tagToken.source, msg => onError(tagToken, 'TAG_RESOLVE_FAILED', msg)); + if (token.type === 'block-seq') { + const { anchor, newlineAfterProp: nl } = props; + const lastProp = anchor && tagToken + ? anchor.offset > tagToken.offset + ? anchor + : tagToken + : (anchor ?? tagToken); + if (lastProp && (!nl || nl.offset < lastProp.offset)) { + const message = 'Missing newline after block sequence props'; + onError(lastProp, 'MISSING_CHAR', message); + } + } + const expType = token.type === 'block-map' + ? 'map' + : token.type === 'block-seq' + ? 'seq' + : token.start.source === '{' + ? 'map' + : 'seq'; + // shortcut: check if it's a generic YAMLMap or YAMLSeq + // before jumping into the custom tag logic. + if (!tagToken || + !tagName || + tagName === '!' || + (tagName === YAMLMap.tagName && expType === 'map') || + (tagName === YAMLSeq.tagName && expType === 'seq')) { + return resolveCollection(CN, ctx, token, onError, tagName); + } + let tag = ctx.schema.tags.find(t => t.tag === tagName && t.collection === expType); + if (!tag) { + const kt = ctx.schema.knownTags[tagName]; + if (kt && kt.collection === expType) { + ctx.schema.tags.push(Object.assign({}, kt, { default: false })); + tag = kt; + } + else { + if (kt?.collection) { + onError(tagToken, 'BAD_COLLECTION_TYPE', `${kt.tag} used for ${expType} collection, but expects ${kt.collection}`, true); + } + else { + onError(tagToken, 'TAG_RESOLVE_FAILED', `Unresolved tag: ${tagName}`, true); + } + return resolveCollection(CN, ctx, token, onError, tagName); + } + } + const coll = resolveCollection(CN, ctx, token, onError, tagName, tag); + const res = tag.resolve?.(coll, msg => onError(tagToken, 'TAG_RESOLVE_FAILED', msg), ctx.options) ?? coll; + const node = isNode(res) + ? res + : new Scalar(res); + node.range = coll.range; + node.tag = tagName; + if (tag?.format) + node.format = tag.format; + return node; +} + +function resolveBlockScalar(ctx, scalar, onError) { + const start = scalar.offset; + const header = parseBlockScalarHeader(scalar, ctx.options.strict, onError); + if (!header) + return { value: '', type: null, comment: '', range: [start, start, start] }; + const type = header.mode === '>' ? Scalar.BLOCK_FOLDED : Scalar.BLOCK_LITERAL; + const lines = scalar.source ? splitLines(scalar.source) : []; + // determine the end of content & start of chomping + let chompStart = lines.length; + for (let i = lines.length - 1; i >= 0; --i) { + const content = lines[i][1]; + if (content === '' || content === '\r') + chompStart = i; + else + break; + } + // shortcut for empty contents + if (chompStart === 0) { + const value = header.chomp === '+' && lines.length > 0 + ? '\n'.repeat(Math.max(1, lines.length - 1)) + : ''; + let end = start + header.length; + if (scalar.source) + end += scalar.source.length; + return { value, type, comment: header.comment, range: [start, end, end] }; + } + // find the indentation level to trim from start + let trimIndent = scalar.indent + header.indent; + let offset = scalar.offset + header.length; + let contentStart = 0; + for (let i = 0; i < chompStart; ++i) { + const [indent, content] = lines[i]; + if (content === '' || content === '\r') { + if (header.indent === 0 && indent.length > trimIndent) + trimIndent = indent.length; + } + else { + if (indent.length < trimIndent) { + const message = 'Block scalars with more-indented leading empty lines must use an explicit indentation indicator'; + onError(offset + indent.length, 'MISSING_CHAR', message); + } + if (header.indent === 0) + trimIndent = indent.length; + contentStart = i; + if (trimIndent === 0 && !ctx.atRoot) { + const message = 'Block scalar values in collections must be indented'; + onError(offset, 'BAD_INDENT', message); + } + break; + } + offset += indent.length + content.length + 1; + } + // include trailing more-indented empty lines in content + for (let i = lines.length - 1; i >= chompStart; --i) { + if (lines[i][0].length > trimIndent) + chompStart = i + 1; + } + let value = ''; + let sep = ''; + let prevMoreIndented = false; + // leading whitespace is kept intact + for (let i = 0; i < contentStart; ++i) + value += lines[i][0].slice(trimIndent) + '\n'; + for (let i = contentStart; i < chompStart; ++i) { + let [indent, content] = lines[i]; + offset += indent.length + content.length + 1; + const crlf = content[content.length - 1] === '\r'; + if (crlf) + content = content.slice(0, -1); + /* istanbul ignore if already caught in lexer */ + if (content && indent.length < trimIndent) { + const src = header.indent + ? 'explicit indentation indicator' + : 'first line'; + const message = `Block scalar lines must not be less indented than their ${src}`; + onError(offset - content.length - (crlf ? 2 : 1), 'BAD_INDENT', message); + indent = ''; + } + if (type === Scalar.BLOCK_LITERAL) { + value += sep + indent.slice(trimIndent) + content; + sep = '\n'; + } + else if (indent.length > trimIndent || content[0] === '\t') { + // more-indented content within a folded block + if (sep === ' ') + sep = '\n'; + else if (!prevMoreIndented && sep === '\n') + sep = '\n\n'; + value += sep + indent.slice(trimIndent) + content; + sep = '\n'; + prevMoreIndented = true; + } + else if (content === '') { + // empty line + if (sep === '\n') + value += '\n'; + else + sep = '\n'; + } + else { + value += sep + content; + sep = ' '; + prevMoreIndented = false; + } + } + switch (header.chomp) { + case '-': + break; + case '+': + for (let i = chompStart; i < lines.length; ++i) + value += '\n' + lines[i][0].slice(trimIndent); + if (value[value.length - 1] !== '\n') + value += '\n'; + break; + default: + value += '\n'; + } + const end = start + header.length + scalar.source.length; + return { value, type, comment: header.comment, range: [start, end, end] }; +} +function parseBlockScalarHeader({ offset, props }, strict, onError) { + /* istanbul ignore if should not happen */ + if (props[0].type !== 'block-scalar-header') { + onError(props[0], 'IMPOSSIBLE', 'Block scalar header not found'); + return null; + } + const { source } = props[0]; + const mode = source[0]; + let indent = 0; + let chomp = ''; + let error = -1; + for (let i = 1; i < source.length; ++i) { + const ch = source[i]; + if (!chomp && (ch === '-' || ch === '+')) + chomp = ch; + else { + const n = Number(ch); + if (!indent && n) + indent = n; + else if (error === -1) + error = offset + i; + } + } + if (error !== -1) + onError(error, 'UNEXPECTED_TOKEN', `Block scalar header includes extra characters: ${source}`); + let hasSpace = false; + let comment = ''; + let length = source.length; + for (let i = 1; i < props.length; ++i) { + const token = props[i]; + switch (token.type) { + case 'space': + hasSpace = true; + // fallthrough + case 'newline': + length += token.source.length; + break; + case 'comment': + if (strict && !hasSpace) { + const message = 'Comments must be separated from other tokens by white space characters'; + onError(token, 'MISSING_CHAR', message); + } + length += token.source.length; + comment = token.source.substring(1); + break; + case 'error': + onError(token, 'UNEXPECTED_TOKEN', token.message); + length += token.source.length; + break; + /* istanbul ignore next should not happen */ + default: { + const message = `Unexpected token in block scalar header: ${token.type}`; + onError(token, 'UNEXPECTED_TOKEN', message); + const ts = token.source; + if (ts && typeof ts === 'string') + length += ts.length; + } + } + } + return { mode, indent, chomp, comment, length }; +} +/** @returns Array of lines split up as `[indent, content]` */ +function splitLines(source) { + const split = source.split(/\n( *)/); + const first = split[0]; + const m = first.match(/^( *)/); + const line0 = m?.[1] + ? [m[1], first.slice(m[1].length)] + : ['', first]; + const lines = [line0]; + for (let i = 1; i < split.length; i += 2) + lines.push([split[i], split[i + 1]]); + return lines; +} + +function resolveFlowScalar(scalar, strict, onError) { + const { offset, type, source, end } = scalar; + let _type; + let value; + const _onError = (rel, code, msg) => onError(offset + rel, code, msg); + switch (type) { + case 'scalar': + _type = Scalar.PLAIN; + value = plainValue(source, _onError); + break; + case 'single-quoted-scalar': + _type = Scalar.QUOTE_SINGLE; + value = singleQuotedValue(source, _onError); + break; + case 'double-quoted-scalar': + _type = Scalar.QUOTE_DOUBLE; + value = doubleQuotedValue(source, _onError); + break; + /* istanbul ignore next should not happen */ + default: + onError(scalar, 'UNEXPECTED_TOKEN', `Expected a flow scalar value, but found: ${type}`); + return { + value: '', + type: null, + comment: '', + range: [offset, offset + source.length, offset + source.length] + }; + } + const valueEnd = offset + source.length; + const re = resolveEnd(end, valueEnd, strict, onError); + return { + value, + type: _type, + comment: re.comment, + range: [offset, valueEnd, re.offset] + }; +} +function plainValue(source, onError) { + let badChar = ''; + switch (source[0]) { + /* istanbul ignore next should not happen */ + case '\t': + badChar = 'a tab character'; + break; + case ',': + badChar = 'flow indicator character ,'; + break; + case '%': + badChar = 'directive indicator character %'; + break; + case '|': + case '>': { + badChar = `block scalar indicator ${source[0]}`; + break; + } + case '@': + case '`': { + badChar = `reserved character ${source[0]}`; + break; + } + } + if (badChar) + onError(0, 'BAD_SCALAR_START', `Plain value cannot start with ${badChar}`); + return foldLines(source); +} +function singleQuotedValue(source, onError) { + if (source[source.length - 1] !== "'" || source.length === 1) + onError(source.length, 'MISSING_CHAR', "Missing closing 'quote"); + return foldLines(source.slice(1, -1)).replace(/''/g, "'"); +} +function foldLines(source) { + /** + * The negative lookbehind here and in the `re` RegExp is to + * prevent causing a polynomial search time in certain cases. + * + * The try-catch is for Safari, which doesn't support this yet: + * https://caniuse.com/js-regexp-lookbehind + */ + let first, line; + try { + first = new RegExp('(.*?)(? wsStart ? source.slice(wsStart, i + 1) : ch; + } + else { + res += ch; + } + } + if (source[source.length - 1] !== '"' || source.length === 1) + onError(source.length, 'MISSING_CHAR', 'Missing closing "quote'); + return res; +} +/** + * Fold a single newline into a space, multiple newlines to N - 1 newlines. + * Presumes `source[offset] === '\n'` + */ +function foldNewline(source, offset) { + let fold = ''; + let ch = source[offset + 1]; + while (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') { + if (ch === '\r' && source[offset + 2] !== '\n') + break; + if (ch === '\n') + fold += '\n'; + offset += 1; + ch = source[offset + 1]; + } + if (!fold) + fold = ' '; + return { fold, offset }; +} +const escapeCodes = { + '0': '\0', // null character + a: '\x07', // bell character + b: '\b', // backspace + e: '\x1b', // escape character + f: '\f', // form feed + n: '\n', // line feed + r: '\r', // carriage return + t: '\t', // horizontal tab + v: '\v', // vertical tab + N: '\u0085', // Unicode next line + _: '\u00a0', // Unicode non-breaking space + L: '\u2028', // Unicode line separator + P: '\u2029', // Unicode paragraph separator + ' ': ' ', + '"': '"', + '/': '/', + '\\': '\\', + '\t': '\t' +}; +function parseCharCode(source, offset, length, onError) { + const cc = source.substr(offset, length); + const ok = cc.length === length && /^[0-9a-fA-F]+$/.test(cc); + const code = ok ? parseInt(cc, 16) : NaN; + if (isNaN(code)) { + const raw = source.substr(offset - 2, length + 2); + onError(offset - 2, 'BAD_DQ_ESCAPE', `Invalid escape sequence ${raw}`); + return raw; + } + return String.fromCodePoint(code); +} + +function composeScalar(ctx, token, tagToken, onError) { + const { value, type, comment, range } = token.type === 'block-scalar' + ? resolveBlockScalar(ctx, token, onError) + : resolveFlowScalar(token, ctx.options.strict, onError); + const tagName = tagToken + ? ctx.directives.tagName(tagToken.source, msg => onError(tagToken, 'TAG_RESOLVE_FAILED', msg)) + : null; + const tag = tagToken && tagName + ? findScalarTagByName(ctx.schema, value, tagName, tagToken, onError) + : token.type === 'scalar' + ? findScalarTagByTest(ctx, value, token, onError) + : ctx.schema[SCALAR$1]; + let scalar; + try { + const res = tag.resolve(value, msg => onError(tagToken ?? token, 'TAG_RESOLVE_FAILED', msg), ctx.options); + scalar = isScalar$1(res) ? res : new Scalar(res); + } + catch (error) { + const msg = error instanceof Error ? error.message : String(error); + onError(tagToken ?? token, 'TAG_RESOLVE_FAILED', msg); + scalar = new Scalar(value); + } + scalar.range = range; + scalar.source = value; + if (type) + scalar.type = type; + if (tagName) + scalar.tag = tagName; + if (tag.format) + scalar.format = tag.format; + if (comment) + scalar.comment = comment; + return scalar; +} +function findScalarTagByName(schema, value, tagName, tagToken, onError) { + if (tagName === '!') + return schema[SCALAR$1]; // non-specific tag + const matchWithTest = []; + for (const tag of schema.tags) { + if (!tag.collection && tag.tag === tagName) { + if (tag.default && tag.test) + matchWithTest.push(tag); + else + return tag; + } + } + for (const tag of matchWithTest) + if (tag.test?.test(value)) + return tag; + const kt = schema.knownTags[tagName]; + if (kt && !kt.collection) { + // Ensure that the known tag is available for stringifying, + // but does not get used by default. + schema.tags.push(Object.assign({}, kt, { default: false, test: undefined })); + return kt; + } + onError(tagToken, 'TAG_RESOLVE_FAILED', `Unresolved tag: ${tagName}`, tagName !== 'tag:yaml.org,2002:str'); + return schema[SCALAR$1]; +} +function findScalarTagByTest({ directives, schema }, value, token, onError) { + const tag = schema.tags.find(tag => tag.default && tag.test?.test(value)) || schema[SCALAR$1]; + if (schema.compat) { + const compat = schema.compat.find(tag => tag.default && tag.test?.test(value)) ?? + schema[SCALAR$1]; + if (tag.tag !== compat.tag) { + const ts = directives.tagString(tag.tag); + const cs = directives.tagString(compat.tag); + const msg = `Value may be parsed as either ${ts} or ${cs}`; + onError(token, 'TAG_RESOLVE_FAILED', msg, true); + } + } + return tag; +} + +function emptyScalarPosition(offset, before, pos) { + if (before) { + if (pos === null) + pos = before.length; + for (let i = pos - 1; i >= 0; --i) { + let st = before[i]; + switch (st.type) { + case 'space': + case 'comment': + case 'newline': + offset -= st.source.length; + continue; + } + // Technically, an empty scalar is immediately after the last non-empty + // node, but it's more useful to place it after any whitespace. + st = before[++i]; + while (st?.type === 'space') { + offset += st.source.length; + st = before[++i]; + } + break; + } + } + return offset; +} + +const CN = { composeNode, composeEmptyNode }; +function composeNode(ctx, token, props, onError) { + const { spaceBefore, comment, anchor, tag } = props; + let node; + let isSrcToken = true; + switch (token.type) { + case 'alias': + node = composeAlias(ctx, token, onError); + if (anchor || tag) + onError(token, 'ALIAS_PROPS', 'An alias node must not specify any properties'); + break; + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': + case 'block-scalar': + node = composeScalar(ctx, token, tag, onError); + if (anchor) + node.anchor = anchor.source.substring(1); + break; + case 'block-map': + case 'block-seq': + case 'flow-collection': + node = composeCollection(CN, ctx, token, props, onError); + if (anchor) + node.anchor = anchor.source.substring(1); + break; + default: { + const message = token.type === 'error' + ? token.message + : `Unsupported token (type: ${token.type})`; + onError(token, 'UNEXPECTED_TOKEN', message); + node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError); + isSrcToken = false; + } + } + if (anchor && node.anchor === '') + onError(anchor, 'BAD_ALIAS', 'Anchor cannot be an empty string'); + if (spaceBefore) + node.spaceBefore = true; + if (comment) { + if (token.type === 'scalar' && token.source === '') + node.comment = comment; + else + node.commentBefore = comment; + } + // @ts-expect-error Type checking misses meaning of isSrcToken + if (ctx.options.keepSourceTokens && isSrcToken) + node.srcToken = token; + return node; +} +function composeEmptyNode(ctx, offset, before, pos, { spaceBefore, comment, anchor, tag, end }, onError) { + const token = { + type: 'scalar', + offset: emptyScalarPosition(offset, before, pos), + indent: -1, + source: '' + }; + const node = composeScalar(ctx, token, tag, onError); + if (anchor) { + node.anchor = anchor.source.substring(1); + if (node.anchor === '') + onError(anchor, 'BAD_ALIAS', 'Anchor cannot be an empty string'); + } + if (spaceBefore) + node.spaceBefore = true; + if (comment) { + node.comment = comment; + node.range[2] = end; + } + return node; +} +function composeAlias({ options }, { offset, source, end }, onError) { + const alias = new Alias(source.substring(1)); + if (alias.source === '') + onError(offset, 'BAD_ALIAS', 'Alias cannot be an empty string'); + if (alias.source.endsWith(':')) + onError(offset + source.length - 1, 'BAD_ALIAS', 'Alias ending in : is ambiguous', true); + const valueEnd = offset + source.length; + const re = resolveEnd(end, valueEnd, options.strict, onError); + alias.range = [offset, valueEnd, re.offset]; + if (re.comment) + alias.comment = re.comment; + return alias; +} + +function composeDoc(options, directives, { offset, start, value, end }, onError) { + const opts = Object.assign({ _directives: directives }, options); + const doc = new Document(undefined, opts); + const ctx = { + atRoot: true, + directives: doc.directives, + options: doc.options, + schema: doc.schema + }; + const props = resolveProps(start, { + indicator: 'doc-start', + next: value ?? end?.[0], + offset, + onError, + parentIndent: 0, + startOnNewline: true + }); + if (props.found) { + doc.directives.docStart = true; + if (value && + (value.type === 'block-map' || value.type === 'block-seq') && + !props.hasNewline) + onError(props.end, 'MISSING_CHAR', 'Block collection cannot start on same line with directives-end marker'); + } + // @ts-expect-error If Contents is set, let's trust the user + doc.contents = value + ? composeNode(ctx, value, props, onError) + : composeEmptyNode(ctx, props.end, start, null, props, onError); + const contentEnd = doc.contents.range[2]; + const re = resolveEnd(end, contentEnd, false, onError); + if (re.comment) + doc.comment = re.comment; + doc.range = [offset, contentEnd, re.offset]; + return doc; +} + +function getErrorPos(src) { + if (typeof src === 'number') + return [src, src + 1]; + if (Array.isArray(src)) + return src.length === 2 ? src : [src[0], src[1]]; + const { offset, source } = src; + return [offset, offset + (typeof source === 'string' ? source.length : 1)]; +} +function parsePrelude(prelude) { + let comment = ''; + let atComment = false; + let afterEmptyLine = false; + for (let i = 0; i < prelude.length; ++i) { + const source = prelude[i]; + switch (source[0]) { + case '#': + comment += + (comment === '' ? '' : afterEmptyLine ? '\n\n' : '\n') + + (source.substring(1) || ' '); + atComment = true; + afterEmptyLine = false; + break; + case '%': + if (prelude[i + 1]?.[0] !== '#') + i += 1; + atComment = false; + break; + default: + // This may be wrong after doc-end, but in that case it doesn't matter + if (!atComment) + afterEmptyLine = true; + atComment = false; + } + } + return { comment, afterEmptyLine }; +} +/** + * Compose a stream of CST nodes into a stream of YAML Documents. + * + * ```ts + * import { Composer, Parser } from 'yaml' + * + * const src: string = ... + * const tokens = new Parser().parse(src) + * const docs = new Composer().compose(tokens) + * ``` + */ +class Composer { + constructor(options = {}) { + this.doc = null; + this.atDirectives = false; + this.prelude = []; + this.errors = []; + this.warnings = []; + this.onError = (source, code, message, warning) => { + const pos = getErrorPos(source); + if (warning) + this.warnings.push(new YAMLWarning(pos, code, message)); + else + this.errors.push(new YAMLParseError(pos, code, message)); + }; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + this.directives = new Directives({ version: options.version || '1.2' }); + this.options = options; + } + decorate(doc, afterDoc) { + const { comment, afterEmptyLine } = parsePrelude(this.prelude); + //console.log({ dc: doc.comment, prelude, comment }) + if (comment) { + const dc = doc.contents; + if (afterDoc) { + doc.comment = doc.comment ? `${doc.comment}\n${comment}` : comment; + } + else if (afterEmptyLine || doc.directives.docStart || !dc) { + doc.commentBefore = comment; + } + else if (isCollection$1(dc) && !dc.flow && dc.items.length > 0) { + let it = dc.items[0]; + if (isPair(it)) + it = it.key; + const cb = it.commentBefore; + it.commentBefore = cb ? `${comment}\n${cb}` : comment; + } + else { + const cb = dc.commentBefore; + dc.commentBefore = cb ? `${comment}\n${cb}` : comment; + } + } + if (afterDoc) { + Array.prototype.push.apply(doc.errors, this.errors); + Array.prototype.push.apply(doc.warnings, this.warnings); + } + else { + doc.errors = this.errors; + doc.warnings = this.warnings; + } + this.prelude = []; + this.errors = []; + this.warnings = []; + } + /** + * Current stream status information. + * + * Mostly useful at the end of input for an empty stream. + */ + streamInfo() { + return { + comment: parsePrelude(this.prelude).comment, + directives: this.directives, + errors: this.errors, + warnings: this.warnings + }; + } + /** + * Compose tokens into documents. + * + * @param forceDoc - If the stream contains no document, still emit a final document including any comments and directives that would be applied to a subsequent document. + * @param endOffset - Should be set if `forceDoc` is also set, to set the document range end and to indicate errors correctly. + */ + *compose(tokens, forceDoc = false, endOffset = -1) { + for (const token of tokens) + yield* this.next(token); + yield* this.end(forceDoc, endOffset); + } + /** Advance the composer by one CST token. */ + *next(token) { + switch (token.type) { + case 'directive': + this.directives.add(token.source, (offset, message, warning) => { + const pos = getErrorPos(token); + pos[0] += offset; + this.onError(pos, 'BAD_DIRECTIVE', message, warning); + }); + this.prelude.push(token.source); + this.atDirectives = true; + break; + case 'document': { + const doc = composeDoc(this.options, this.directives, token, this.onError); + if (this.atDirectives && !doc.directives.docStart) + this.onError(token, 'MISSING_CHAR', 'Missing directives-end/doc-start indicator line'); + this.decorate(doc, false); + if (this.doc) + yield this.doc; + this.doc = doc; + this.atDirectives = false; + break; + } + case 'byte-order-mark': + case 'space': + break; + case 'comment': + case 'newline': + this.prelude.push(token.source); + break; + case 'error': { + const msg = token.source + ? `${token.message}: ${JSON.stringify(token.source)}` + : token.message; + const error = new YAMLParseError(getErrorPos(token), 'UNEXPECTED_TOKEN', msg); + if (this.atDirectives || !this.doc) + this.errors.push(error); + else + this.doc.errors.push(error); + break; + } + case 'doc-end': { + if (!this.doc) { + const msg = 'Unexpected doc-end without preceding document'; + this.errors.push(new YAMLParseError(getErrorPos(token), 'UNEXPECTED_TOKEN', msg)); + break; + } + this.doc.directives.docEnd = true; + const end = resolveEnd(token.end, token.offset + token.source.length, this.doc.options.strict, this.onError); + this.decorate(this.doc, true); + if (end.comment) { + const dc = this.doc.comment; + this.doc.comment = dc ? `${dc}\n${end.comment}` : end.comment; + } + this.doc.range[2] = end.offset; + break; + } + default: + this.errors.push(new YAMLParseError(getErrorPos(token), 'UNEXPECTED_TOKEN', `Unsupported token ${token.type}`)); + } + } + /** + * Call at end of input to yield any remaining document. + * + * @param forceDoc - If the stream contains no document, still emit a final document including any comments and directives that would be applied to a subsequent document. + * @param endOffset - Should be set if `forceDoc` is also set, to set the document range end and to indicate errors correctly. + */ + *end(forceDoc = false, endOffset = -1) { + if (this.doc) { + this.decorate(this.doc, true); + yield this.doc; + this.doc = null; + } + else if (forceDoc) { + const opts = Object.assign({ _directives: this.directives }, this.options); + const doc = new Document(undefined, opts); + if (this.atDirectives) + this.onError(endOffset, 'MISSING_CHAR', 'Missing directives-end indicator line'); + doc.range = [0, endOffset, endOffset]; + this.decorate(doc, false); + yield doc; + } + } +} + +function resolveAsScalar(token, strict = true, onError) { + if (token) { + const _onError = (pos, code, message) => { + const offset = typeof pos === 'number' ? pos : Array.isArray(pos) ? pos[0] : pos.offset; + if (onError) + onError(offset, code, message); + else + throw new YAMLParseError([offset, offset + 1], code, message); + }; + switch (token.type) { + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': + return resolveFlowScalar(token, strict, _onError); + case 'block-scalar': + return resolveBlockScalar({ options: { strict } }, token, _onError); + } + } + return null; +} +/** + * Create a new scalar token with `value` + * + * Values that represent an actual string but may be parsed as a different type should use a `type` other than `'PLAIN'`, + * as this function does not support any schema operations and won't check for such conflicts. + * + * @param value The string representation of the value, which will have its content properly indented. + * @param context.end Comments and whitespace after the end of the value, or after the block scalar header. If undefined, a newline will be added. + * @param context.implicitKey Being within an implicit key may affect the resolved type of the token's value. + * @param context.indent The indent level of the token. + * @param context.inFlow Is this scalar within a flow collection? This may affect the resolved type of the token's value. + * @param context.offset The offset position of the token. + * @param context.type The preferred type of the scalar token. If undefined, the previous type of the `token` will be used, defaulting to `'PLAIN'`. + */ +function createScalarToken(value, context) { + const { implicitKey = false, indent, inFlow = false, offset = -1, type = 'PLAIN' } = context; + const source = stringifyString({ type, value }, { + implicitKey, + indent: indent > 0 ? ' '.repeat(indent) : '', + inFlow, + options: { blockQuote: true, lineWidth: -1 } + }); + const end = context.end ?? [ + { type: 'newline', offset: -1, indent, source: '\n' } + ]; + switch (source[0]) { + case '|': + case '>': { + const he = source.indexOf('\n'); + const head = source.substring(0, he); + const body = source.substring(he + 1) + '\n'; + const props = [ + { type: 'block-scalar-header', offset, indent, source: head } + ]; + if (!addEndtoBlockProps(props, end)) + props.push({ type: 'newline', offset: -1, indent, source: '\n' }); + return { type: 'block-scalar', offset, indent, props, source: body }; + } + case '"': + return { type: 'double-quoted-scalar', offset, indent, source, end }; + case "'": + return { type: 'single-quoted-scalar', offset, indent, source, end }; + default: + return { type: 'scalar', offset, indent, source, end }; + } +} +/** + * Set the value of `token` to the given string `value`, overwriting any previous contents and type that it may have. + * + * Best efforts are made to retain any comments previously associated with the `token`, + * though all contents within a collection's `items` will be overwritten. + * + * Values that represent an actual string but may be parsed as a different type should use a `type` other than `'PLAIN'`, + * as this function does not support any schema operations and won't check for such conflicts. + * + * @param token Any token. If it does not include an `indent` value, the value will be stringified as if it were an implicit key. + * @param value The string representation of the value, which will have its content properly indented. + * @param context.afterKey In most cases, values after a key should have an additional level of indentation. + * @param context.implicitKey Being within an implicit key may affect the resolved type of the token's value. + * @param context.inFlow Being within a flow collection may affect the resolved type of the token's value. + * @param context.type The preferred type of the scalar token. If undefined, the previous type of the `token` will be used, defaulting to `'PLAIN'`. + */ +function setScalarValue(token, value, context = {}) { + let { afterKey = false, implicitKey = false, inFlow = false, type } = context; + let indent = 'indent' in token ? token.indent : null; + if (afterKey && typeof indent === 'number') + indent += 2; + if (!type) + switch (token.type) { + case 'single-quoted-scalar': + type = 'QUOTE_SINGLE'; + break; + case 'double-quoted-scalar': + type = 'QUOTE_DOUBLE'; + break; + case 'block-scalar': { + const header = token.props[0]; + if (header.type !== 'block-scalar-header') + throw new Error('Invalid block scalar header'); + type = header.source[0] === '>' ? 'BLOCK_FOLDED' : 'BLOCK_LITERAL'; + break; + } + default: + type = 'PLAIN'; + } + const source = stringifyString({ type, value }, { + implicitKey: implicitKey || indent === null, + indent: indent !== null && indent > 0 ? ' '.repeat(indent) : '', + inFlow, + options: { blockQuote: true, lineWidth: -1 } + }); + switch (source[0]) { + case '|': + case '>': + setBlockScalarValue(token, source); + break; + case '"': + setFlowScalarValue(token, source, 'double-quoted-scalar'); + break; + case "'": + setFlowScalarValue(token, source, 'single-quoted-scalar'); + break; + default: + setFlowScalarValue(token, source, 'scalar'); + } +} +function setBlockScalarValue(token, source) { + const he = source.indexOf('\n'); + const head = source.substring(0, he); + const body = source.substring(he + 1) + '\n'; + if (token.type === 'block-scalar') { + const header = token.props[0]; + if (header.type !== 'block-scalar-header') + throw new Error('Invalid block scalar header'); + header.source = head; + token.source = body; + } + else { + const { offset } = token; + const indent = 'indent' in token ? token.indent : -1; + const props = [ + { type: 'block-scalar-header', offset, indent, source: head } + ]; + if (!addEndtoBlockProps(props, 'end' in token ? token.end : undefined)) + props.push({ type: 'newline', offset: -1, indent, source: '\n' }); + for (const key of Object.keys(token)) + if (key !== 'type' && key !== 'offset') + delete token[key]; + Object.assign(token, { type: 'block-scalar', indent, props, source: body }); + } +} +/** @returns `true` if last token is a newline */ +function addEndtoBlockProps(props, end) { + if (end) + for (const st of end) + switch (st.type) { + case 'space': + case 'comment': + props.push(st); + break; + case 'newline': + props.push(st); + return true; + } + return false; +} +function setFlowScalarValue(token, source, type) { + switch (token.type) { + case 'scalar': + case 'double-quoted-scalar': + case 'single-quoted-scalar': + token.type = type; + token.source = source; + break; + case 'block-scalar': { + const end = token.props.slice(1); + let oa = source.length; + if (token.props[0].type === 'block-scalar-header') + oa -= token.props[0].source.length; + for (const tok of end) + tok.offset += oa; + delete token.props; + Object.assign(token, { type, source, end }); + break; + } + case 'block-map': + case 'block-seq': { + const offset = token.offset + source.length; + const nl = { type: 'newline', offset, indent: token.indent, source: '\n' }; + delete token.items; + Object.assign(token, { type, source, end: [nl] }); + break; + } + default: { + const indent = 'indent' in token ? token.indent : -1; + const end = 'end' in token && Array.isArray(token.end) + ? token.end.filter(st => st.type === 'space' || + st.type === 'comment' || + st.type === 'newline') + : []; + for (const key of Object.keys(token)) + if (key !== 'type' && key !== 'offset') + delete token[key]; + Object.assign(token, { type, indent, source, end }); + } + } +} + +/** + * Stringify a CST document, token, or collection item + * + * Fair warning: This applies no validation whatsoever, and + * simply concatenates the sources in their logical order. + */ +const stringify$1 = (cst) => 'type' in cst ? stringifyToken(cst) : stringifyItem(cst); +function stringifyToken(token) { + switch (token.type) { + case 'block-scalar': { + let res = ''; + for (const tok of token.props) + res += stringifyToken(tok); + return res + token.source; + } + case 'block-map': + case 'block-seq': { + let res = ''; + for (const item of token.items) + res += stringifyItem(item); + return res; + } + case 'flow-collection': { + let res = token.start.source; + for (const item of token.items) + res += stringifyItem(item); + for (const st of token.end) + res += st.source; + return res; + } + case 'document': { + let res = stringifyItem(token); + if (token.end) + for (const st of token.end) + res += st.source; + return res; + } + default: { + let res = token.source; + if ('end' in token && token.end) + for (const st of token.end) + res += st.source; + return res; + } + } +} +function stringifyItem({ start, key, sep, value }) { + let res = ''; + for (const st of start) + res += st.source; + if (key) + res += stringifyToken(key); + if (sep) + for (const st of sep) + res += st.source; + if (value) + res += stringifyToken(value); + return res; +} + +const BREAK = Symbol('break visit'); +const SKIP = Symbol('skip children'); +const REMOVE = Symbol('remove item'); +/** + * Apply a visitor to a CST document or item. + * + * Walks through the tree (depth-first) starting from the root, calling a + * `visitor` function with two arguments when entering each item: + * - `item`: The current item, which included the following members: + * - `start: SourceToken[]` – Source tokens before the key or value, + * possibly including its anchor or tag. + * - `key?: Token | null` – Set for pair values. May then be `null`, if + * the key before the `:` separator is empty. + * - `sep?: SourceToken[]` – Source tokens between the key and the value, + * which should include the `:` map value indicator if `value` is set. + * - `value?: Token` – The value of a sequence item, or of a map pair. + * - `path`: The steps from the root to the current node, as an array of + * `['key' | 'value', number]` tuples. + * + * The return value of the visitor may be used to control the traversal: + * - `undefined` (default): Do nothing and continue + * - `visit.SKIP`: Do not visit the children of this token, continue with + * next sibling + * - `visit.BREAK`: Terminate traversal completely + * - `visit.REMOVE`: Remove the current item, then continue with the next one + * - `number`: Set the index of the next step. This is useful especially if + * the index of the current token has changed. + * - `function`: Define the next visitor for this item. After the original + * visitor is called on item entry, next visitors are called after handling + * a non-empty `key` and when exiting the item. + */ +function visit(cst, visitor) { + if ('type' in cst && cst.type === 'document') + cst = { start: cst.start, value: cst.value }; + _visit(Object.freeze([]), cst, visitor); +} +// Without the `as symbol` casts, TS declares these in the `visit` +// namespace using `var`, but then complains about that because +// `unique symbol` must be `const`. +/** Terminate visit traversal completely */ +visit.BREAK = BREAK; +/** Do not visit the children of the current item */ +visit.SKIP = SKIP; +/** Remove the current item */ +visit.REMOVE = REMOVE; +/** Find the item at `path` from `cst` as the root */ +visit.itemAtPath = (cst, path) => { + let item = cst; + for (const [field, index] of path) { + const tok = item?.[field]; + if (tok && 'items' in tok) { + item = tok.items[index]; + } + else + return undefined; + } + return item; +}; +/** + * Get the immediate parent collection of the item at `path` from `cst` as the root. + * + * Throws an error if the collection is not found, which should never happen if the item itself exists. + */ +visit.parentCollection = (cst, path) => { + const parent = visit.itemAtPath(cst, path.slice(0, -1)); + const field = path[path.length - 1][0]; + const coll = parent?.[field]; + if (coll && 'items' in coll) + return coll; + throw new Error('Parent collection not found'); +}; +function _visit(path, item, visitor) { + let ctrl = visitor(item, path); + if (typeof ctrl === 'symbol') + return ctrl; + for (const field of ['key', 'value']) { + const token = item[field]; + if (token && 'items' in token) { + for (let i = 0; i < token.items.length; ++i) { + const ci = _visit(Object.freeze(path.concat([[field, i]])), token.items[i], visitor); + if (typeof ci === 'number') + i = ci - 1; + else if (ci === BREAK) + return BREAK; + else if (ci === REMOVE) { + token.items.splice(i, 1); + i -= 1; + } + } + if (typeof ctrl === 'function' && field === 'key') + ctrl = ctrl(item, path); + } + } + return typeof ctrl === 'function' ? ctrl(item, path) : ctrl; +} + +/** The byte order mark */ +const BOM = '\u{FEFF}'; +/** Start of doc-mode */ +const DOCUMENT = '\x02'; // C0: Start of Text +/** Unexpected end of flow-mode */ +const FLOW_END = '\x18'; // C0: Cancel +/** Next token is a scalar value */ +const SCALAR = '\x1f'; // C0: Unit Separator +/** @returns `true` if `token` is a flow or block collection */ +const isCollection = (token) => !!token && 'items' in token; +/** @returns `true` if `token` is a flow or block scalar; not an alias */ +const isScalar = (token) => !!token && + (token.type === 'scalar' || + token.type === 'single-quoted-scalar' || + token.type === 'double-quoted-scalar' || + token.type === 'block-scalar'); +/* istanbul ignore next */ +/** Get a printable representation of a lexer token */ +function prettyToken(token) { + switch (token) { + case BOM: + return ''; + case DOCUMENT: + return ''; + case FLOW_END: + return ''; + case SCALAR: + return ''; + default: + return JSON.stringify(token); + } +} +/** Identify the type of a lexer token. May return `null` for unknown tokens. */ +function tokenType(source) { + switch (source) { + case BOM: + return 'byte-order-mark'; + case DOCUMENT: + return 'doc-mode'; + case FLOW_END: + return 'flow-error-end'; + case SCALAR: + return 'scalar'; + case '---': + return 'doc-start'; + case '...': + return 'doc-end'; + case '': + case '\n': + case '\r\n': + return 'newline'; + case '-': + return 'seq-item-ind'; + case '?': + return 'explicit-key-ind'; + case ':': + return 'map-value-ind'; + case '{': + return 'flow-map-start'; + case '}': + return 'flow-map-end'; + case '[': + return 'flow-seq-start'; + case ']': + return 'flow-seq-end'; + case ',': + return 'comma'; + } + switch (source[0]) { + case ' ': + case '\t': + return 'space'; + case '#': + return 'comment'; + case '%': + return 'directive-line'; + case '*': + return 'alias'; + case '&': + return 'anchor'; + case '!': + return 'tag'; + case "'": + return 'single-quoted-scalar'; + case '"': + return 'double-quoted-scalar'; + case '|': + case '>': + return 'block-scalar-header'; + } + return null; +} + +var cst = /*#__PURE__*/Object.freeze({ + __proto__: null, + BOM: BOM, + DOCUMENT: DOCUMENT, + FLOW_END: FLOW_END, + SCALAR: SCALAR, + createScalarToken: createScalarToken, + isCollection: isCollection, + isScalar: isScalar, + prettyToken: prettyToken, + resolveAsScalar: resolveAsScalar, + setScalarValue: setScalarValue, + stringify: stringify$1, + tokenType: tokenType, + visit: visit +}); + +/* +START -> stream + +stream + directive -> line-end -> stream + indent + line-end -> stream + [else] -> line-start + +line-end + comment -> line-end + newline -> . + input-end -> END + +line-start + doc-start -> doc + doc-end -> stream + [else] -> indent -> block-start + +block-start + seq-item-start -> block-start + explicit-key-start -> block-start + map-value-start -> block-start + [else] -> doc + +doc + line-end -> line-start + spaces -> doc + anchor -> doc + tag -> doc + flow-start -> flow -> doc + flow-end -> error -> doc + seq-item-start -> error -> doc + explicit-key-start -> error -> doc + map-value-start -> doc + alias -> doc + quote-start -> quoted-scalar -> doc + block-scalar-header -> line-end -> block-scalar(min) -> line-start + [else] -> plain-scalar(false, min) -> doc + +flow + line-end -> flow + spaces -> flow + anchor -> flow + tag -> flow + flow-start -> flow -> flow + flow-end -> . + seq-item-start -> error -> flow + explicit-key-start -> flow + map-value-start -> flow + alias -> flow + quote-start -> quoted-scalar -> flow + comma -> flow + [else] -> plain-scalar(true, 0) -> flow + +quoted-scalar + quote-end -> . + [else] -> quoted-scalar + +block-scalar(min) + newline + peek(indent < min) -> . + [else] -> block-scalar(min) + +plain-scalar(is-flow, min) + scalar-end(is-flow) -> . + peek(newline + (indent < min)) -> . + [else] -> plain-scalar(min) +*/ +function isEmpty(ch) { + switch (ch) { + case undefined: + case ' ': + case '\n': + case '\r': + case '\t': + return true; + default: + return false; + } +} +const hexDigits = new Set('0123456789ABCDEFabcdef'); +const tagChars = new Set("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-#;/?:@&=+$_.!~*'()"); +const flowIndicatorChars = new Set(',[]{}'); +const invalidAnchorChars = new Set(' ,[]{}\n\r\t'); +const isNotAnchorChar = (ch) => !ch || invalidAnchorChars.has(ch); +/** + * Splits an input string into lexical tokens, i.e. smaller strings that are + * easily identifiable by `tokens.tokenType()`. + * + * Lexing starts always in a "stream" context. Incomplete input may be buffered + * until a complete token can be emitted. + * + * In addition to slices of the original input, the following control characters + * may also be emitted: + * + * - `\x02` (Start of Text): A document starts with the next token + * - `\x18` (Cancel): Unexpected end of flow-mode (indicates an error) + * - `\x1f` (Unit Separator): Next token is a scalar value + * - `\u{FEFF}` (Byte order mark): Emitted separately outside documents + */ +class Lexer { + constructor() { + /** + * Flag indicating whether the end of the current buffer marks the end of + * all input + */ + this.atEnd = false; + /** + * Explicit indent set in block scalar header, as an offset from the current + * minimum indent, so e.g. set to 1 from a header `|2+`. Set to -1 if not + * explicitly set. + */ + this.blockScalarIndent = -1; + /** + * Block scalars that include a + (keep) chomping indicator in their header + * include trailing empty lines, which are otherwise excluded from the + * scalar's contents. + */ + this.blockScalarKeep = false; + /** Current input */ + this.buffer = ''; + /** + * Flag noting whether the map value indicator : can immediately follow this + * node within a flow context. + */ + this.flowKey = false; + /** Count of surrounding flow collection levels. */ + this.flowLevel = 0; + /** + * Minimum level of indentation required for next lines to be parsed as a + * part of the current scalar value. + */ + this.indentNext = 0; + /** Indentation level of the current line. */ + this.indentValue = 0; + /** Position of the next \n character. */ + this.lineEndPos = null; + /** Stores the state of the lexer if reaching the end of incpomplete input */ + this.next = null; + /** A pointer to `buffer`; the current position of the lexer. */ + this.pos = 0; + } + /** + * Generate YAML tokens from the `source` string. If `incomplete`, + * a part of the last line may be left as a buffer for the next call. + * + * @returns A generator of lexical tokens + */ + *lex(source, incomplete = false) { + if (source) { + if (typeof source !== 'string') + throw TypeError('source is not a string'); + this.buffer = this.buffer ? this.buffer + source : source; + this.lineEndPos = null; + } + this.atEnd = !incomplete; + let next = this.next ?? 'stream'; + while (next && (incomplete || this.hasChars(1))) + next = yield* this.parseNext(next); + } + atLineEnd() { + let i = this.pos; + let ch = this.buffer[i]; + while (ch === ' ' || ch === '\t') + ch = this.buffer[++i]; + if (!ch || ch === '#' || ch === '\n') + return true; + if (ch === '\r') + return this.buffer[i + 1] === '\n'; + return false; + } + charAt(n) { + return this.buffer[this.pos + n]; + } + continueScalar(offset) { + let ch = this.buffer[offset]; + if (this.indentNext > 0) { + let indent = 0; + while (ch === ' ') + ch = this.buffer[++indent + offset]; + if (ch === '\r') { + const next = this.buffer[indent + offset + 1]; + if (next === '\n' || (!next && !this.atEnd)) + return offset + indent + 1; + } + return ch === '\n' || indent >= this.indentNext || (!ch && !this.atEnd) + ? offset + indent + : -1; + } + if (ch === '-' || ch === '.') { + const dt = this.buffer.substr(offset, 3); + if ((dt === '---' || dt === '...') && isEmpty(this.buffer[offset + 3])) + return -1; + } + return offset; + } + getLine() { + let end = this.lineEndPos; + if (typeof end !== 'number' || (end !== -1 && end < this.pos)) { + end = this.buffer.indexOf('\n', this.pos); + this.lineEndPos = end; + } + if (end === -1) + return this.atEnd ? this.buffer.substring(this.pos) : null; + if (this.buffer[end - 1] === '\r') + end -= 1; + return this.buffer.substring(this.pos, end); + } + hasChars(n) { + return this.pos + n <= this.buffer.length; + } + setNext(state) { + this.buffer = this.buffer.substring(this.pos); + this.pos = 0; + this.lineEndPos = null; + this.next = state; + return null; + } + peek(n) { + return this.buffer.substr(this.pos, n); + } + *parseNext(next) { + switch (next) { + case 'stream': + return yield* this.parseStream(); + case 'line-start': + return yield* this.parseLineStart(); + case 'block-start': + return yield* this.parseBlockStart(); + case 'doc': + return yield* this.parseDocument(); + case 'flow': + return yield* this.parseFlowCollection(); + case 'quoted-scalar': + return yield* this.parseQuotedScalar(); + case 'block-scalar': + return yield* this.parseBlockScalar(); + case 'plain-scalar': + return yield* this.parsePlainScalar(); + } + } + *parseStream() { + let line = this.getLine(); + if (line === null) + return this.setNext('stream'); + if (line[0] === BOM) { + yield* this.pushCount(1); + line = line.substring(1); + } + if (line[0] === '%') { + let dirEnd = line.length; + let cs = line.indexOf('#'); + while (cs !== -1) { + const ch = line[cs - 1]; + if (ch === ' ' || ch === '\t') { + dirEnd = cs - 1; + break; + } + else { + cs = line.indexOf('#', cs + 1); + } + } + while (true) { + const ch = line[dirEnd - 1]; + if (ch === ' ' || ch === '\t') + dirEnd -= 1; + else + break; + } + const n = (yield* this.pushCount(dirEnd)) + (yield* this.pushSpaces(true)); + yield* this.pushCount(line.length - n); // possible comment + this.pushNewline(); + return 'stream'; + } + if (this.atLineEnd()) { + const sp = yield* this.pushSpaces(true); + yield* this.pushCount(line.length - sp); + yield* this.pushNewline(); + return 'stream'; + } + yield DOCUMENT; + return yield* this.parseLineStart(); + } + *parseLineStart() { + const ch = this.charAt(0); + if (!ch && !this.atEnd) + return this.setNext('line-start'); + if (ch === '-' || ch === '.') { + if (!this.atEnd && !this.hasChars(4)) + return this.setNext('line-start'); + const s = this.peek(3); + if ((s === '---' || s === '...') && isEmpty(this.charAt(3))) { + yield* this.pushCount(3); + this.indentValue = 0; + this.indentNext = 0; + return s === '---' ? 'doc' : 'stream'; + } + } + this.indentValue = yield* this.pushSpaces(false); + if (this.indentNext > this.indentValue && !isEmpty(this.charAt(1))) + this.indentNext = this.indentValue; + return yield* this.parseBlockStart(); + } + *parseBlockStart() { + const [ch0, ch1] = this.peek(2); + if (!ch1 && !this.atEnd) + return this.setNext('block-start'); + if ((ch0 === '-' || ch0 === '?' || ch0 === ':') && isEmpty(ch1)) { + const n = (yield* this.pushCount(1)) + (yield* this.pushSpaces(true)); + this.indentNext = this.indentValue + 1; + this.indentValue += n; + return yield* this.parseBlockStart(); + } + return 'doc'; + } + *parseDocument() { + yield* this.pushSpaces(true); + const line = this.getLine(); + if (line === null) + return this.setNext('doc'); + let n = yield* this.pushIndicators(); + switch (line[n]) { + case '#': + yield* this.pushCount(line.length - n); + // fallthrough + case undefined: + yield* this.pushNewline(); + return yield* this.parseLineStart(); + case '{': + case '[': + yield* this.pushCount(1); + this.flowKey = false; + this.flowLevel = 1; + return 'flow'; + case '}': + case ']': + // this is an error + yield* this.pushCount(1); + return 'doc'; + case '*': + yield* this.pushUntil(isNotAnchorChar); + return 'doc'; + case '"': + case "'": + return yield* this.parseQuotedScalar(); + case '|': + case '>': + n += yield* this.parseBlockScalarHeader(); + n += yield* this.pushSpaces(true); + yield* this.pushCount(line.length - n); + yield* this.pushNewline(); + return yield* this.parseBlockScalar(); + default: + return yield* this.parsePlainScalar(); + } + } + *parseFlowCollection() { + let nl, sp; + let indent = -1; + do { + nl = yield* this.pushNewline(); + if (nl > 0) { + sp = yield* this.pushSpaces(false); + this.indentValue = indent = sp; + } + else { + sp = 0; + } + sp += yield* this.pushSpaces(true); + } while (nl + sp > 0); + const line = this.getLine(); + if (line === null) + return this.setNext('flow'); + if ((indent !== -1 && indent < this.indentNext && line[0] !== '#') || + (indent === 0 && + (line.startsWith('---') || line.startsWith('...')) && + isEmpty(line[3]))) { + // Allowing for the terminal ] or } at the same (rather than greater) + // indent level as the initial [ or { is technically invalid, but + // failing here would be surprising to users. + const atFlowEndMarker = indent === this.indentNext - 1 && + this.flowLevel === 1 && + (line[0] === ']' || line[0] === '}'); + if (!atFlowEndMarker) { + // this is an error + this.flowLevel = 0; + yield FLOW_END; + return yield* this.parseLineStart(); + } + } + let n = 0; + while (line[n] === ',') { + n += yield* this.pushCount(1); + n += yield* this.pushSpaces(true); + this.flowKey = false; + } + n += yield* this.pushIndicators(); + switch (line[n]) { + case undefined: + return 'flow'; + case '#': + yield* this.pushCount(line.length - n); + return 'flow'; + case '{': + case '[': + yield* this.pushCount(1); + this.flowKey = false; + this.flowLevel += 1; + return 'flow'; + case '}': + case ']': + yield* this.pushCount(1); + this.flowKey = true; + this.flowLevel -= 1; + return this.flowLevel ? 'flow' : 'doc'; + case '*': + yield* this.pushUntil(isNotAnchorChar); + return 'flow'; + case '"': + case "'": + this.flowKey = true; + return yield* this.parseQuotedScalar(); + case ':': { + const next = this.charAt(1); + if (this.flowKey || isEmpty(next) || next === ',') { + this.flowKey = false; + yield* this.pushCount(1); + yield* this.pushSpaces(true); + return 'flow'; + } + } + // fallthrough + default: + this.flowKey = false; + return yield* this.parsePlainScalar(); + } + } + *parseQuotedScalar() { + const quote = this.charAt(0); + let end = this.buffer.indexOf(quote, this.pos + 1); + if (quote === "'") { + while (end !== -1 && this.buffer[end + 1] === "'") + end = this.buffer.indexOf("'", end + 2); + } + else { + // double-quote + while (end !== -1) { + let n = 0; + while (this.buffer[end - 1 - n] === '\\') + n += 1; + if (n % 2 === 0) + break; + end = this.buffer.indexOf('"', end + 1); + } + } + // Only looking for newlines within the quotes + const qb = this.buffer.substring(0, end); + let nl = qb.indexOf('\n', this.pos); + if (nl !== -1) { + while (nl !== -1) { + const cs = this.continueScalar(nl + 1); + if (cs === -1) + break; + nl = qb.indexOf('\n', cs); + } + if (nl !== -1) { + // this is an error caused by an unexpected unindent + end = nl - (qb[nl - 1] === '\r' ? 2 : 1); + } + } + if (end === -1) { + if (!this.atEnd) + return this.setNext('quoted-scalar'); + end = this.buffer.length; + } + yield* this.pushToIndex(end + 1, false); + return this.flowLevel ? 'flow' : 'doc'; + } + *parseBlockScalarHeader() { + this.blockScalarIndent = -1; + this.blockScalarKeep = false; + let i = this.pos; + while (true) { + const ch = this.buffer[++i]; + if (ch === '+') + this.blockScalarKeep = true; + else if (ch > '0' && ch <= '9') + this.blockScalarIndent = Number(ch) - 1; + else if (ch !== '-') + break; + } + return yield* this.pushUntil(ch => isEmpty(ch) || ch === '#'); + } + *parseBlockScalar() { + let nl = this.pos - 1; // may be -1 if this.pos === 0 + let indent = 0; + let ch; + loop: for (let i = this.pos; (ch = this.buffer[i]); ++i) { + switch (ch) { + case ' ': + indent += 1; + break; + case '\n': + nl = i; + indent = 0; + break; + case '\r': { + const next = this.buffer[i + 1]; + if (!next && !this.atEnd) + return this.setNext('block-scalar'); + if (next === '\n') + break; + } // fallthrough + default: + break loop; + } + } + if (!ch && !this.atEnd) + return this.setNext('block-scalar'); + if (indent >= this.indentNext) { + if (this.blockScalarIndent === -1) + this.indentNext = indent; + else { + this.indentNext = + this.blockScalarIndent + (this.indentNext === 0 ? 1 : this.indentNext); + } + do { + const cs = this.continueScalar(nl + 1); + if (cs === -1) + break; + nl = this.buffer.indexOf('\n', cs); + } while (nl !== -1); + if (nl === -1) { + if (!this.atEnd) + return this.setNext('block-scalar'); + nl = this.buffer.length; + } + } + // Trailing insufficiently indented tabs are invalid. + // To catch that during parsing, we include them in the block scalar value. + let i = nl + 1; + ch = this.buffer[i]; + while (ch === ' ') + ch = this.buffer[++i]; + if (ch === '\t') { + while (ch === '\t' || ch === ' ' || ch === '\r' || ch === '\n') + ch = this.buffer[++i]; + nl = i - 1; + } + else if (!this.blockScalarKeep) { + do { + let i = nl - 1; + let ch = this.buffer[i]; + if (ch === '\r') + ch = this.buffer[--i]; + const lastChar = i; // Drop the line if last char not more indented + while (ch === ' ') + ch = this.buffer[--i]; + if (ch === '\n' && i >= this.pos && i + 1 + indent > lastChar) + nl = i; + else + break; + } while (true); + } + yield SCALAR; + yield* this.pushToIndex(nl + 1, true); + return yield* this.parseLineStart(); + } + *parsePlainScalar() { + const inFlow = this.flowLevel > 0; + let end = this.pos - 1; + let i = this.pos - 1; + let ch; + while ((ch = this.buffer[++i])) { + if (ch === ':') { + const next = this.buffer[i + 1]; + if (isEmpty(next) || (inFlow && flowIndicatorChars.has(next))) + break; + end = i; + } + else if (isEmpty(ch)) { + let next = this.buffer[i + 1]; + if (ch === '\r') { + if (next === '\n') { + i += 1; + ch = '\n'; + next = this.buffer[i + 1]; + } + else + end = i; + } + if (next === '#' || (inFlow && flowIndicatorChars.has(next))) + break; + if (ch === '\n') { + const cs = this.continueScalar(i + 1); + if (cs === -1) + break; + i = Math.max(i, cs - 2); // to advance, but still account for ' #' + } + } + else { + if (inFlow && flowIndicatorChars.has(ch)) + break; + end = i; + } + } + if (!ch && !this.atEnd) + return this.setNext('plain-scalar'); + yield SCALAR; + yield* this.pushToIndex(end + 1, true); + return inFlow ? 'flow' : 'doc'; + } + *pushCount(n) { + if (n > 0) { + yield this.buffer.substr(this.pos, n); + this.pos += n; + return n; + } + return 0; + } + *pushToIndex(i, allowEmpty) { + const s = this.buffer.slice(this.pos, i); + if (s) { + yield s; + this.pos += s.length; + return s.length; + } + else if (allowEmpty) + yield ''; + return 0; + } + *pushIndicators() { + switch (this.charAt(0)) { + case '!': + return ((yield* this.pushTag()) + + (yield* this.pushSpaces(true)) + + (yield* this.pushIndicators())); + case '&': + return ((yield* this.pushUntil(isNotAnchorChar)) + + (yield* this.pushSpaces(true)) + + (yield* this.pushIndicators())); + case '-': // this is an error + case '?': // this is an error outside flow collections + case ':': { + const inFlow = this.flowLevel > 0; + const ch1 = this.charAt(1); + if (isEmpty(ch1) || (inFlow && flowIndicatorChars.has(ch1))) { + if (!inFlow) + this.indentNext = this.indentValue + 1; + else if (this.flowKey) + this.flowKey = false; + return ((yield* this.pushCount(1)) + + (yield* this.pushSpaces(true)) + + (yield* this.pushIndicators())); + } + } + } + return 0; + } + *pushTag() { + if (this.charAt(1) === '<') { + let i = this.pos + 2; + let ch = this.buffer[i]; + while (!isEmpty(ch) && ch !== '>') + ch = this.buffer[++i]; + return yield* this.pushToIndex(ch === '>' ? i + 1 : i, false); + } + else { + let i = this.pos + 1; + let ch = this.buffer[i]; + while (ch) { + if (tagChars.has(ch)) + ch = this.buffer[++i]; + else if (ch === '%' && + hexDigits.has(this.buffer[i + 1]) && + hexDigits.has(this.buffer[i + 2])) { + ch = this.buffer[(i += 3)]; + } + else + break; + } + return yield* this.pushToIndex(i, false); + } + } + *pushNewline() { + const ch = this.buffer[this.pos]; + if (ch === '\n') + return yield* this.pushCount(1); + else if (ch === '\r' && this.charAt(1) === '\n') + return yield* this.pushCount(2); + else + return 0; + } + *pushSpaces(allowTabs) { + let i = this.pos - 1; + let ch; + do { + ch = this.buffer[++i]; + } while (ch === ' ' || (allowTabs && ch === '\t')); + const n = i - this.pos; + if (n > 0) { + yield this.buffer.substr(this.pos, n); + this.pos = i; + } + return n; + } + *pushUntil(test) { + let i = this.pos; + let ch = this.buffer[i]; + while (!test(ch)) + ch = this.buffer[++i]; + return yield* this.pushToIndex(i, false); + } +} + +/** + * Tracks newlines during parsing in order to provide an efficient API for + * determining the one-indexed `{ line, col }` position for any offset + * within the input. + */ +class LineCounter { + constructor() { + this.lineStarts = []; + /** + * Should be called in ascending order. Otherwise, call + * `lineCounter.lineStarts.sort()` before calling `linePos()`. + */ + this.addNewLine = (offset) => this.lineStarts.push(offset); + /** + * Performs a binary search and returns the 1-indexed { line, col } + * position of `offset`. If `line === 0`, `addNewLine` has never been + * called or `offset` is before the first known newline. + */ + this.linePos = (offset) => { + let low = 0; + let high = this.lineStarts.length; + while (low < high) { + const mid = (low + high) >> 1; // Math.floor((low + high) / 2) + if (this.lineStarts[mid] < offset) + low = mid + 1; + else + high = mid; + } + if (this.lineStarts[low] === offset) + return { line: low + 1, col: 1 }; + if (low === 0) + return { line: 0, col: offset }; + const start = this.lineStarts[low - 1]; + return { line: low, col: offset - start + 1 }; + }; + } +} + +function includesToken(list, type) { + for (let i = 0; i < list.length; ++i) + if (list[i].type === type) + return true; + return false; +} +function findNonEmptyIndex(list) { + for (let i = 0; i < list.length; ++i) { + switch (list[i].type) { + case 'space': + case 'comment': + case 'newline': + break; + default: + return i; + } + } + return -1; +} +function isFlowToken(token) { + switch (token?.type) { + case 'alias': + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': + case 'flow-collection': + return true; + default: + return false; + } +} +function getPrevProps(parent) { + switch (parent.type) { + case 'document': + return parent.start; + case 'block-map': { + const it = parent.items[parent.items.length - 1]; + return it.sep ?? it.start; + } + case 'block-seq': + return parent.items[parent.items.length - 1].start; + /* istanbul ignore next should not happen */ + default: + return []; + } +} +/** Note: May modify input array */ +function getFirstKeyStartProps(prev) { + if (prev.length === 0) + return []; + let i = prev.length; + loop: while (--i >= 0) { + switch (prev[i].type) { + case 'doc-start': + case 'explicit-key-ind': + case 'map-value-ind': + case 'seq-item-ind': + case 'newline': + break loop; + } + } + while (prev[++i]?.type === 'space') { + /* loop */ + } + return prev.splice(i, prev.length); +} +function fixFlowSeqItems(fc) { + if (fc.start.type === 'flow-seq-start') { + for (const it of fc.items) { + if (it.sep && + !it.value && + !includesToken(it.start, 'explicit-key-ind') && + !includesToken(it.sep, 'map-value-ind')) { + if (it.key) + it.value = it.key; + delete it.key; + if (isFlowToken(it.value)) { + if (it.value.end) + Array.prototype.push.apply(it.value.end, it.sep); + else + it.value.end = it.sep; + } + else + Array.prototype.push.apply(it.start, it.sep); + delete it.sep; + } + } + } +} +/** + * A YAML concrete syntax tree (CST) parser + * + * ```ts + * const src: string = ... + * for (const token of new Parser().parse(src)) { + * // token: Token + * } + * ``` + * + * To use the parser with a user-provided lexer: + * + * ```ts + * function* parse(source: string, lexer: Lexer) { + * const parser = new Parser() + * for (const lexeme of lexer.lex(source)) + * yield* parser.next(lexeme) + * yield* parser.end() + * } + * + * const src: string = ... + * const lexer = new Lexer() + * for (const token of parse(src, lexer)) { + * // token: Token + * } + * ``` + */ +class Parser { + /** + * @param onNewLine - If defined, called separately with the start position of + * each new line (in `parse()`, including the start of input). + */ + constructor(onNewLine) { + /** If true, space and sequence indicators count as indentation */ + this.atNewLine = true; + /** If true, next token is a scalar value */ + this.atScalar = false; + /** Current indentation level */ + this.indent = 0; + /** Current offset since the start of parsing */ + this.offset = 0; + /** On the same line with a block map key */ + this.onKeyLine = false; + /** Top indicates the node that's currently being built */ + this.stack = []; + /** The source of the current token, set in parse() */ + this.source = ''; + /** The type of the current token, set in parse() */ + this.type = ''; + // Must be defined after `next()` + this.lexer = new Lexer(); + this.onNewLine = onNewLine; + } + /** + * Parse `source` as a YAML stream. + * If `incomplete`, a part of the last line may be left as a buffer for the next call. + * + * Errors are not thrown, but yielded as `{ type: 'error', message }` tokens. + * + * @returns A generator of tokens representing each directive, document, and other structure. + */ + *parse(source, incomplete = false) { + if (this.onNewLine && this.offset === 0) + this.onNewLine(0); + for (const lexeme of this.lexer.lex(source, incomplete)) + yield* this.next(lexeme); + if (!incomplete) + yield* this.end(); + } + /** + * Advance the parser by the `source` of one lexical token. + */ + *next(source) { + this.source = source; + if (this.atScalar) { + this.atScalar = false; + yield* this.step(); + this.offset += source.length; + return; + } + const type = tokenType(source); + if (!type) { + const message = `Not a YAML token: ${source}`; + yield* this.pop({ type: 'error', offset: this.offset, message, source }); + this.offset += source.length; + } + else if (type === 'scalar') { + this.atNewLine = false; + this.atScalar = true; + this.type = 'scalar'; + } + else { + this.type = type; + yield* this.step(); + switch (type) { + case 'newline': + this.atNewLine = true; + this.indent = 0; + if (this.onNewLine) + this.onNewLine(this.offset + source.length); + break; + case 'space': + if (this.atNewLine && source[0] === ' ') + this.indent += source.length; + break; + case 'explicit-key-ind': + case 'map-value-ind': + case 'seq-item-ind': + if (this.atNewLine) + this.indent += source.length; + break; + case 'doc-mode': + case 'flow-error-end': + return; + default: + this.atNewLine = false; + } + this.offset += source.length; + } + } + /** Call at end of input to push out any remaining constructions */ + *end() { + while (this.stack.length > 0) + yield* this.pop(); + } + get sourceToken() { + const st = { + type: this.type, + offset: this.offset, + indent: this.indent, + source: this.source + }; + return st; + } + *step() { + const top = this.peek(1); + if (this.type === 'doc-end' && (!top || top.type !== 'doc-end')) { + while (this.stack.length > 0) + yield* this.pop(); + this.stack.push({ + type: 'doc-end', + offset: this.offset, + source: this.source + }); + return; + } + if (!top) + return yield* this.stream(); + switch (top.type) { + case 'document': + return yield* this.document(top); + case 'alias': + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': + return yield* this.scalar(top); + case 'block-scalar': + return yield* this.blockScalar(top); + case 'block-map': + return yield* this.blockMap(top); + case 'block-seq': + return yield* this.blockSequence(top); + case 'flow-collection': + return yield* this.flowCollection(top); + case 'doc-end': + return yield* this.documentEnd(top); + } + /* istanbul ignore next should not happen */ + yield* this.pop(); + } + peek(n) { + return this.stack[this.stack.length - n]; + } + *pop(error) { + const token = error ?? this.stack.pop(); + /* istanbul ignore if should not happen */ + if (!token) { + const message = 'Tried to pop an empty stack'; + yield { type: 'error', offset: this.offset, source: '', message }; + } + else if (this.stack.length === 0) { + yield token; + } + else { + const top = this.peek(1); + if (token.type === 'block-scalar') { + // Block scalars use their parent rather than header indent + token.indent = 'indent' in top ? top.indent : 0; + } + else if (token.type === 'flow-collection' && top.type === 'document') { + // Ignore all indent for top-level flow collections + token.indent = 0; + } + if (token.type === 'flow-collection') + fixFlowSeqItems(token); + switch (top.type) { + case 'document': + top.value = token; + break; + case 'block-scalar': + top.props.push(token); // error + break; + case 'block-map': { + const it = top.items[top.items.length - 1]; + if (it.value) { + top.items.push({ start: [], key: token, sep: [] }); + this.onKeyLine = true; + return; + } + else if (it.sep) { + it.value = token; + } + else { + Object.assign(it, { key: token, sep: [] }); + this.onKeyLine = !it.explicitKey; + return; + } + break; + } + case 'block-seq': { + const it = top.items[top.items.length - 1]; + if (it.value) + top.items.push({ start: [], value: token }); + else + it.value = token; + break; + } + case 'flow-collection': { + const it = top.items[top.items.length - 1]; + if (!it || it.value) + top.items.push({ start: [], key: token, sep: [] }); + else if (it.sep) + it.value = token; + else + Object.assign(it, { key: token, sep: [] }); + return; + } + /* istanbul ignore next should not happen */ + default: + yield* this.pop(); + yield* this.pop(token); + } + if ((top.type === 'document' || + top.type === 'block-map' || + top.type === 'block-seq') && + (token.type === 'block-map' || token.type === 'block-seq')) { + const last = token.items[token.items.length - 1]; + if (last && + !last.sep && + !last.value && + last.start.length > 0 && + findNonEmptyIndex(last.start) === -1 && + (token.indent === 0 || + last.start.every(st => st.type !== 'comment' || st.indent < token.indent))) { + if (top.type === 'document') + top.end = last.start; + else + top.items.push({ start: last.start }); + token.items.splice(-1, 1); + } + } + } + } + *stream() { + switch (this.type) { + case 'directive-line': + yield { type: 'directive', offset: this.offset, source: this.source }; + return; + case 'byte-order-mark': + case 'space': + case 'comment': + case 'newline': + yield this.sourceToken; + return; + case 'doc-mode': + case 'doc-start': { + const doc = { + type: 'document', + offset: this.offset, + start: [] + }; + if (this.type === 'doc-start') + doc.start.push(this.sourceToken); + this.stack.push(doc); + return; + } + } + yield { + type: 'error', + offset: this.offset, + message: `Unexpected ${this.type} token in YAML stream`, + source: this.source + }; + } + *document(doc) { + if (doc.value) + return yield* this.lineEnd(doc); + switch (this.type) { + case 'doc-start': { + if (findNonEmptyIndex(doc.start) !== -1) { + yield* this.pop(); + yield* this.step(); + } + else + doc.start.push(this.sourceToken); + return; + } + case 'anchor': + case 'tag': + case 'space': + case 'comment': + case 'newline': + doc.start.push(this.sourceToken); + return; + } + const bv = this.startBlockValue(doc); + if (bv) + this.stack.push(bv); + else { + yield { + type: 'error', + offset: this.offset, + message: `Unexpected ${this.type} token in YAML document`, + source: this.source + }; + } + } + *scalar(scalar) { + if (this.type === 'map-value-ind') { + const prev = getPrevProps(this.peek(2)); + const start = getFirstKeyStartProps(prev); + let sep; + if (scalar.end) { + sep = scalar.end; + sep.push(this.sourceToken); + delete scalar.end; + } + else + sep = [this.sourceToken]; + const map = { + type: 'block-map', + offset: scalar.offset, + indent: scalar.indent, + items: [{ start, key: scalar, sep }] + }; + this.onKeyLine = true; + this.stack[this.stack.length - 1] = map; + } + else + yield* this.lineEnd(scalar); + } + *blockScalar(scalar) { + switch (this.type) { + case 'space': + case 'comment': + case 'newline': + scalar.props.push(this.sourceToken); + return; + case 'scalar': + scalar.source = this.source; + // block-scalar source includes trailing newline + this.atNewLine = true; + this.indent = 0; + if (this.onNewLine) { + let nl = this.source.indexOf('\n') + 1; + while (nl !== 0) { + this.onNewLine(this.offset + nl); + nl = this.source.indexOf('\n', nl) + 1; + } + } + yield* this.pop(); + break; + /* istanbul ignore next should not happen */ + default: + yield* this.pop(); + yield* this.step(); + } + } + *blockMap(map) { + const it = map.items[map.items.length - 1]; + // it.sep is true-ish if pair already has key or : separator + switch (this.type) { + case 'newline': + this.onKeyLine = false; + if (it.value) { + const end = 'end' in it.value ? it.value.end : undefined; + const last = Array.isArray(end) ? end[end.length - 1] : undefined; + if (last?.type === 'comment') + end?.push(this.sourceToken); + else + map.items.push({ start: [this.sourceToken] }); + } + else if (it.sep) { + it.sep.push(this.sourceToken); + } + else { + it.start.push(this.sourceToken); + } + return; + case 'space': + case 'comment': + if (it.value) { + map.items.push({ start: [this.sourceToken] }); + } + else if (it.sep) { + it.sep.push(this.sourceToken); + } + else { + if (this.atIndentedComment(it.start, map.indent)) { + const prev = map.items[map.items.length - 2]; + const end = prev?.value?.end; + if (Array.isArray(end)) { + Array.prototype.push.apply(end, it.start); + end.push(this.sourceToken); + map.items.pop(); + return; + } + } + it.start.push(this.sourceToken); + } + return; + } + if (this.indent >= map.indent) { + const atMapIndent = !this.onKeyLine && this.indent === map.indent; + const atNextItem = atMapIndent && + (it.sep || it.explicitKey) && + this.type !== 'seq-item-ind'; + // For empty nodes, assign newline-separated not indented empty tokens to following node + let start = []; + if (atNextItem && it.sep && !it.value) { + const nl = []; + for (let i = 0; i < it.sep.length; ++i) { + const st = it.sep[i]; + switch (st.type) { + case 'newline': + nl.push(i); + break; + case 'space': + break; + case 'comment': + if (st.indent > map.indent) + nl.length = 0; + break; + default: + nl.length = 0; + } + } + if (nl.length >= 2) + start = it.sep.splice(nl[1]); + } + switch (this.type) { + case 'anchor': + case 'tag': + if (atNextItem || it.value) { + start.push(this.sourceToken); + map.items.push({ start }); + this.onKeyLine = true; + } + else if (it.sep) { + it.sep.push(this.sourceToken); + } + else { + it.start.push(this.sourceToken); + } + return; + case 'explicit-key-ind': + if (!it.sep && !it.explicitKey) { + it.start.push(this.sourceToken); + it.explicitKey = true; + } + else if (atNextItem || it.value) { + start.push(this.sourceToken); + map.items.push({ start, explicitKey: true }); + } + else { + this.stack.push({ + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start: [this.sourceToken], explicitKey: true }] + }); + } + this.onKeyLine = true; + return; + case 'map-value-ind': + if (it.explicitKey) { + if (!it.sep) { + if (includesToken(it.start, 'newline')) { + Object.assign(it, { key: null, sep: [this.sourceToken] }); + } + else { + const start = getFirstKeyStartProps(it.start); + this.stack.push({ + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start, key: null, sep: [this.sourceToken] }] + }); + } + } + else if (it.value) { + map.items.push({ start: [], key: null, sep: [this.sourceToken] }); + } + else if (includesToken(it.sep, 'map-value-ind')) { + this.stack.push({ + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start, key: null, sep: [this.sourceToken] }] + }); + } + else if (isFlowToken(it.key) && + !includesToken(it.sep, 'newline')) { + const start = getFirstKeyStartProps(it.start); + const key = it.key; + const sep = it.sep; + sep.push(this.sourceToken); + // @ts-expect-error type guard is wrong here + delete it.key; + // @ts-expect-error type guard is wrong here + delete it.sep; + this.stack.push({ + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start, key, sep }] + }); + } + else if (start.length > 0) { + // Not actually at next item + it.sep = it.sep.concat(start, this.sourceToken); + } + else { + it.sep.push(this.sourceToken); + } + } + else { + if (!it.sep) { + Object.assign(it, { key: null, sep: [this.sourceToken] }); + } + else if (it.value || atNextItem) { + map.items.push({ start, key: null, sep: [this.sourceToken] }); + } + else if (includesToken(it.sep, 'map-value-ind')) { + this.stack.push({ + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start: [], key: null, sep: [this.sourceToken] }] + }); + } + else { + it.sep.push(this.sourceToken); + } + } + this.onKeyLine = true; + return; + case 'alias': + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': { + const fs = this.flowScalar(this.type); + if (atNextItem || it.value) { + map.items.push({ start, key: fs, sep: [] }); + this.onKeyLine = true; + } + else if (it.sep) { + this.stack.push(fs); + } + else { + Object.assign(it, { key: fs, sep: [] }); + this.onKeyLine = true; + } + return; + } + default: { + const bv = this.startBlockValue(map); + if (bv) { + if (atMapIndent && bv.type !== 'block-seq') { + map.items.push({ start }); + } + this.stack.push(bv); + return; + } + } + } + } + yield* this.pop(); + yield* this.step(); + } + *blockSequence(seq) { + const it = seq.items[seq.items.length - 1]; + switch (this.type) { + case 'newline': + if (it.value) { + const end = 'end' in it.value ? it.value.end : undefined; + const last = Array.isArray(end) ? end[end.length - 1] : undefined; + if (last?.type === 'comment') + end?.push(this.sourceToken); + else + seq.items.push({ start: [this.sourceToken] }); + } + else + it.start.push(this.sourceToken); + return; + case 'space': + case 'comment': + if (it.value) + seq.items.push({ start: [this.sourceToken] }); + else { + if (this.atIndentedComment(it.start, seq.indent)) { + const prev = seq.items[seq.items.length - 2]; + const end = prev?.value?.end; + if (Array.isArray(end)) { + Array.prototype.push.apply(end, it.start); + end.push(this.sourceToken); + seq.items.pop(); + return; + } + } + it.start.push(this.sourceToken); + } + return; + case 'anchor': + case 'tag': + if (it.value || this.indent <= seq.indent) + break; + it.start.push(this.sourceToken); + return; + case 'seq-item-ind': + if (this.indent !== seq.indent) + break; + if (it.value || includesToken(it.start, 'seq-item-ind')) + seq.items.push({ start: [this.sourceToken] }); + else + it.start.push(this.sourceToken); + return; + } + if (this.indent > seq.indent) { + const bv = this.startBlockValue(seq); + if (bv) { + this.stack.push(bv); + return; + } + } + yield* this.pop(); + yield* this.step(); + } + *flowCollection(fc) { + const it = fc.items[fc.items.length - 1]; + if (this.type === 'flow-error-end') { + let top; + do { + yield* this.pop(); + top = this.peek(1); + } while (top && top.type === 'flow-collection'); + } + else if (fc.end.length === 0) { + switch (this.type) { + case 'comma': + case 'explicit-key-ind': + if (!it || it.sep) + fc.items.push({ start: [this.sourceToken] }); + else + it.start.push(this.sourceToken); + return; + case 'map-value-ind': + if (!it || it.value) + fc.items.push({ start: [], key: null, sep: [this.sourceToken] }); + else if (it.sep) + it.sep.push(this.sourceToken); + else + Object.assign(it, { key: null, sep: [this.sourceToken] }); + return; + case 'space': + case 'comment': + case 'newline': + case 'anchor': + case 'tag': + if (!it || it.value) + fc.items.push({ start: [this.sourceToken] }); + else if (it.sep) + it.sep.push(this.sourceToken); + else + it.start.push(this.sourceToken); + return; + case 'alias': + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': { + const fs = this.flowScalar(this.type); + if (!it || it.value) + fc.items.push({ start: [], key: fs, sep: [] }); + else if (it.sep) + this.stack.push(fs); + else + Object.assign(it, { key: fs, sep: [] }); + return; + } + case 'flow-map-end': + case 'flow-seq-end': + fc.end.push(this.sourceToken); + return; + } + const bv = this.startBlockValue(fc); + /* istanbul ignore else should not happen */ + if (bv) + this.stack.push(bv); + else { + yield* this.pop(); + yield* this.step(); + } + } + else { + const parent = this.peek(2); + if (parent.type === 'block-map' && + ((this.type === 'map-value-ind' && parent.indent === fc.indent) || + (this.type === 'newline' && + !parent.items[parent.items.length - 1].sep))) { + yield* this.pop(); + yield* this.step(); + } + else if (this.type === 'map-value-ind' && + parent.type !== 'flow-collection') { + const prev = getPrevProps(parent); + const start = getFirstKeyStartProps(prev); + fixFlowSeqItems(fc); + const sep = fc.end.splice(1, fc.end.length); + sep.push(this.sourceToken); + const map = { + type: 'block-map', + offset: fc.offset, + indent: fc.indent, + items: [{ start, key: fc, sep }] + }; + this.onKeyLine = true; + this.stack[this.stack.length - 1] = map; + } + else { + yield* this.lineEnd(fc); + } + } + } + flowScalar(type) { + if (this.onNewLine) { + let nl = this.source.indexOf('\n') + 1; + while (nl !== 0) { + this.onNewLine(this.offset + nl); + nl = this.source.indexOf('\n', nl) + 1; + } + } + return { + type, + offset: this.offset, + indent: this.indent, + source: this.source + }; + } + startBlockValue(parent) { + switch (this.type) { + case 'alias': + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': + return this.flowScalar(this.type); + case 'block-scalar-header': + return { + type: 'block-scalar', + offset: this.offset, + indent: this.indent, + props: [this.sourceToken], + source: '' + }; + case 'flow-map-start': + case 'flow-seq-start': + return { + type: 'flow-collection', + offset: this.offset, + indent: this.indent, + start: this.sourceToken, + items: [], + end: [] + }; + case 'seq-item-ind': + return { + type: 'block-seq', + offset: this.offset, + indent: this.indent, + items: [{ start: [this.sourceToken] }] + }; + case 'explicit-key-ind': { + this.onKeyLine = true; + const prev = getPrevProps(parent); + const start = getFirstKeyStartProps(prev); + start.push(this.sourceToken); + return { + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start, explicitKey: true }] + }; + } + case 'map-value-ind': { + this.onKeyLine = true; + const prev = getPrevProps(parent); + const start = getFirstKeyStartProps(prev); + return { + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start, key: null, sep: [this.sourceToken] }] + }; + } + } + return null; + } + atIndentedComment(start, indent) { + if (this.type !== 'comment') + return false; + if (this.indent <= indent) + return false; + return start.every(st => st.type === 'newline' || st.type === 'space'); + } + *documentEnd(docEnd) { + if (this.type !== 'doc-mode') { + if (docEnd.end) + docEnd.end.push(this.sourceToken); + else + docEnd.end = [this.sourceToken]; + if (this.type === 'newline') + yield* this.pop(); + } + } + *lineEnd(token) { + switch (this.type) { + case 'comma': + case 'doc-start': + case 'doc-end': + case 'flow-seq-end': + case 'flow-map-end': + case 'map-value-ind': + yield* this.pop(); + yield* this.step(); + break; + case 'newline': + this.onKeyLine = false; + // fallthrough + case 'space': + case 'comment': + default: + // all other values are errors + if (token.end) + token.end.push(this.sourceToken); + else + token.end = [this.sourceToken]; + if (this.type === 'newline') + yield* this.pop(); + } + } +} + +function parseOptions(options) { + const prettyErrors = options.prettyErrors !== false; + const lineCounter = options.lineCounter || (prettyErrors && new LineCounter()) || null; + return { lineCounter, prettyErrors }; +} +/** + * Parse the input as a stream of YAML documents. + * + * Documents should be separated from each other by `...` or `---` marker lines. + * + * @returns If an empty `docs` array is returned, it will be of type + * EmptyStream and contain additional stream information. In + * TypeScript, you should use `'empty' in docs` as a type guard for it. + */ +function parseAllDocuments(source, options = {}) { + const { lineCounter, prettyErrors } = parseOptions(options); + const parser = new Parser(lineCounter?.addNewLine); + const composer = new Composer(options); + const docs = Array.from(composer.compose(parser.parse(source))); + if (prettyErrors && lineCounter) + for (const doc of docs) { + doc.errors.forEach(prettifyError(source, lineCounter)); + doc.warnings.forEach(prettifyError(source, lineCounter)); + } + if (docs.length > 0) + return docs; + return Object.assign([], { empty: true }, composer.streamInfo()); +} +/** Parse an input string into a single YAML.Document */ +function parseDocument(source, options = {}) { + const { lineCounter, prettyErrors } = parseOptions(options); + const parser = new Parser(lineCounter?.addNewLine); + const composer = new Composer(options); + // `doc` is always set by compose.end(true) at the very latest + let doc = null; + for (const _doc of composer.compose(parser.parse(source), true, source.length)) { + if (!doc) + doc = _doc; + else if (doc.options.logLevel !== 'silent') { + doc.errors.push(new YAMLParseError(_doc.range.slice(0, 2), 'MULTIPLE_DOCS', 'Source contains multiple documents; please use YAML.parseAllDocuments()')); + break; + } + } + if (prettyErrors && lineCounter) { + doc.errors.forEach(prettifyError(source, lineCounter)); + doc.warnings.forEach(prettifyError(source, lineCounter)); + } + return doc; +} +function parse(src, reviver, options) { + let _reviver = undefined; + if (typeof reviver === 'function') { + _reviver = reviver; + } + else if (options === undefined && reviver && typeof reviver === 'object') { + options = reviver; + } + const doc = parseDocument(src, options); + if (!doc) + return null; + doc.warnings.forEach(warning => warn(doc.options.logLevel, warning)); + if (doc.errors.length > 0) { + if (doc.options.logLevel !== 'silent') + throw doc.errors[0]; + else + doc.errors = []; + } + return doc.toJS(Object.assign({ reviver: _reviver }, options)); +} +function stringify(value, replacer, options) { + let _replacer = null; + if (typeof replacer === 'function' || Array.isArray(replacer)) { + _replacer = replacer; + } + else if (options === undefined && replacer) { + options = replacer; + } + if (typeof options === 'string') + options = options.length; + if (typeof options === 'number') { + const indent = Math.round(options); + options = indent < 1 ? undefined : indent > 8 ? { indent: 8 } : { indent }; + } + if (value === undefined) { + const { keepUndefined } = options ?? replacer ?? {}; + if (!keepUndefined) + return undefined; + } + return new Document(value, _replacer, options).toString(options); +} + +var YAML = /*#__PURE__*/Object.freeze({ + __proto__: null, + Alias: Alias, + CST: cst, + Composer: Composer, + Document: Document, + Lexer: Lexer, + LineCounter: LineCounter, + Pair: Pair, + Parser: Parser, + Scalar: Scalar, + Schema: Schema, + YAMLError: YAMLError, + YAMLMap: YAMLMap, + YAMLParseError: YAMLParseError, + YAMLSeq: YAMLSeq, + YAMLWarning: YAMLWarning, + isAlias: isAlias, + isCollection: isCollection$1, + isDocument: isDocument, + isMap: isMap, + isNode: isNode, + isPair: isPair, + isScalar: isScalar$1, + isSeq: isSeq, + parse: parse, + parseAllDocuments: parseAllDocuments, + parseDocument: parseDocument, + stringify: stringify, + visit: visit$1, + visitAsync: visitAsync +}); + +// `export * as default from ...` fails on Webpack v4 +// https://github.com/eemeli/yaml/issues/228 + +export { Alias, cst as CST, Composer, Document, Lexer, LineCounter, Pair, Parser, Scalar, Schema, YAMLError, YAMLMap, YAMLParseError, YAMLSeq, YAMLWarning, YAML as default, isAlias, isCollection$1 as isCollection, isDocument, isMap, isNode, isPair, isScalar$1 as isScalar, isSeq, parse, parseAllDocuments, parseDocument, stringify, visit$1 as visit, visitAsync }; diff --git a/dist/src/api-intercept.d.ts b/dist/src/api-intercept.d.ts new file mode 100644 index 00000000..2345fdcc --- /dev/null +++ b/dist/src/api-intercept.d.ts @@ -0,0 +1,51 @@ +import { BaseViewConfig, ApiInterceptFlags, EmbedEvent } from "./types"; +/** + * Returns the data to be sent to embed to setup intercepts + * the urls to intercept, timeout etc + * @param viewConfig + * @returns + */ +export declare const getInterceptInitData: (viewConfig: BaseViewConfig) => Required>; +/** + * Handle Api intercept event and simulate legacy onBeforeGetVizDataIntercept event + * + * embed sends -> ApiIntercept -> we send + * ApiIntercept + * OnBeforeGetVizDataIntercept (if url is part of DefaultUrlMap.AnswerData) + * + * @param params + * @returns + */ +export declare const handleInterceptEvent: (params: { + eventData: any; + executeEvent: (eventType: EmbedEvent, data: any) => void; + viewConfig: BaseViewConfig; + getUnsavedAnswerTml: (props: { + sessionId?: string; + vizId?: string; + }) => Promise<{ + tml: string; + }>; +}) => Promise; +/** + * Support both the legacy and new format of the api intercept response + * @param payload + * @returns + */ +export declare const processApiInterceptResponse: (payload: any) => any; +export declare const processLegacyInterceptResponse: (payload: any) => { + data: { + execute: any; + response: { + body: { + errors: { + title: any; + description: any; + isUserError: boolean; + }[]; + data: {}; + }; + }; + }; +}; +//# sourceMappingURL=api-intercept.d.ts.map \ No newline at end of file diff --git a/dist/src/api-intercept.d.ts.map b/dist/src/api-intercept.d.ts.map new file mode 100644 index 00000000..0c695d28 --- /dev/null +++ b/dist/src/api-intercept.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"api-intercept.d.ts","sourceRoot":"","sources":["api-intercept.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsB,cAAc,EAAE,iBAAiB,EAAE,UAAU,EAA8D,MAAM,SAAS,CAAC;AAwCxJ;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,eAAgB,cAAc,KAAG,SAAS,KAAK,iBAAiB,EAAE,sCAAsC,CAAC,CAgBzI,CAAA;AA6CD;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB;eAClB,GAAG;8BACY,UAAU,QAAQ,GAAG,KAAK,IAAI;gBAC5C,cAAc;iCACG;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,QAAQ;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;mBA4CnG,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,YAAa,GAAG,QAQvD,CAAA;AAED,eAAO,MAAM,8BAA8B,YAAa,GAAG;;;;;;;;;;;;;;CAuB1D,CAAA"} \ No newline at end of file diff --git a/dist/src/api-intercept.spec.d.ts b/dist/src/api-intercept.spec.d.ts new file mode 100644 index 00000000..4d6de0cb --- /dev/null +++ b/dist/src/api-intercept.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=api-intercept.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/api-intercept.spec.d.ts.map b/dist/src/api-intercept.spec.d.ts.map new file mode 100644 index 00000000..65afcb21 --- /dev/null +++ b/dist/src/api-intercept.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"api-intercept.spec.d.ts","sourceRoot":"","sources":["api-intercept.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/auth.d.ts b/dist/src/auth.d.ts new file mode 100644 index 00000000..78e955a2 --- /dev/null +++ b/dist/src/auth.d.ts @@ -0,0 +1,241 @@ +import EventEmitter from 'eventemitter3'; +import { EmbedConfig } from './types'; +export declare let loggedInStatus: boolean; +export declare let samlAuthWindow: Window; +export declare let samlCompletionPromise: Promise; +export declare const SSO_REDIRECTION_MARKER_GUID = "5e16222e-ef02-43e9-9fbd-24226bf3ce5b"; +/** + * Enum for auth failure types. + * This value is passed to the listener for {@link AuthStatus.FAILURE}. + * @group Authentication / Init + */ +export declare enum AuthFailureType { + /** + * Authentication failed in the SDK authentication flow. + * + * Emitted when `init()` or auto-authentication cannot establish a logged-in session. + * For example, this can happen because of an invalid token, an auth request failure, + * or an auth promise rejection. + */ + SDK = "SDK", + /** + * Browser cookie access is blocked for the embedded app. + * + * Emitted when the iframe reports that required cookies + * cannot be read or sent, commonly due to third-party cookie restrictions. + */ + NO_COOKIE_ACCESS = "NO_COOKIE_ACCESS", + /** + * The current authentication token or session has expired. + * + * Emitted when the embed receives an auth-expiry signal and starts auth refresh + * handling. + */ + EXPIRY = "EXPIRY", + /** + * A generic authentication failure that does not match a more specific type. + * + * Emitted as a fallback for app-reported auth failures in standard auth flows. + */ + OTHER = "OTHER", + /** + * The user session timed out due to inactivity. + * + * Emitted when the app reports an idle-session timeout. + */ + IDLE_SESSION_TIMEOUT = "IDLE_SESSION_TIMEOUT", + /** + * The app reports that the user is unauthenticated. + * + * Used primarily to classify unauthenticated failures in Embedded SSO flows. + */ + UNAUTHENTICATED_FAILURE = "UNAUTHENTICATED_FAILURE" +} +/** + * Enum for auth status emitted by the emitter returned from {@link init}. + * @group Authentication / Init + */ +export declare enum AuthStatus { + /** + * Emits when the SDK fails to authenticate. + */ + FAILURE = "FAILURE", + /** + * Emits when the SDK authentication step completes + * successfully (e.g., token exchange, cookie set). + * This fires before any iframe is rendered. Use + * this to know that auth passed and it is safe to + * proceed with rendering. The callback receives no + * arguments. + * @example + * ```js + * const authEE = init({ ... }); + * authEE.on(AuthStatus.SDK_SUCCESS, () => { + * // Auth done, iframe not loaded yet + * }); + * ``` + */ + SDK_SUCCESS = "SDK_SUCCESS", + /** + * @hidden + * Emits when iframe is loaded and session + * information is available. + */ + SESSION_INFO_SUCCESS = "SESSION_INFO_SUCCESS", + /** + * Emits when the ThoughtSpot app inside the + * embedded iframe confirms its session is active. + * This fires after the iframe loads and sends back an `AuthInit` event. + * @param sessionInfo Information about the user session, with details like `userGUID`. + * @see EmbedEvent.AuthInit + * @example + * ```js + * const authEE = init({ ... }); + * authEE.on(AuthStatus.SUCCESS, (sessionInfo) => { + * // App is loaded and authenticated + * console.log(sessionInfo.userGUID); + * }); + * ``` + */ + SUCCESS = "SUCCESS", + /** + * Emits when a user logs out + */ + LOGOUT = "LOGOUT", + /** + * Emitted when inPopup is true in the SAMLRedirect flow and the + * popup is waiting to be triggered either programmatically + * or by the trigger button. + * @version SDK: 1.19.0 + */ + WAITING_FOR_POPUP = "WAITING_FOR_POPUP", + /** + * Emitted when the SAML popup is closed without authentication + */ + SAML_POPUP_CLOSED_NO_AUTH = "SAML_POPUP_CLOSED_NO_AUTH" +} +/** + * Event emitter returned from {@link init}. + * @group Authentication / Init + */ +export interface AuthEventEmitter { + /** + * Register a listener on Auth failure. + * @param event + * @param listener + */ + on(event: AuthStatus.FAILURE, listener: (failureType: AuthFailureType) => void): this; + /** + * Register a listener on Auth SDK success. + * @param event + * @param listener + */ + on(event: AuthStatus.SDK_SUCCESS | AuthStatus.LOGOUT | AuthStatus.WAITING_FOR_POPUP | AuthStatus.SAML_POPUP_CLOSED_NO_AUTH, listener: () => void): this; + on(event: AuthStatus.SUCCESS, listener: (sessionInfo: any) => void): this; + once(event: AuthStatus.FAILURE, listener: (failureType: AuthFailureType) => void): this; + once(event: AuthStatus.SDK_SUCCESS | AuthStatus.LOGOUT | AuthStatus.WAITING_FOR_POPUP | AuthStatus.SAML_POPUP_CLOSED_NO_AUTH, listener: () => void): this; + once(event: AuthStatus.SUCCESS, listener: (sessionInfo: any) => void): this; + /** + * Trigger an event on the emitter returned from init. + * @param {@link AuthEvent} + */ + emit(event: AuthEvent, ...args: any[]): boolean; + /** + * Remove listener from the emitter returned from init. + * @param event + * @param listener + * @param context + * @param once + */ + off(event: AuthStatus, listener: (...args: any[]) => void, context: any, once: boolean): this; + /** + * Remove all the event listeners + * @param event + */ + removeAllListeners(event: AuthStatus): this; +} +/** + * Events which can be triggered on the emitter returned from {@link init}. + * @group Authentication / Init + */ +export declare enum AuthEvent { + /** + * Manually trigger the SSO popup. This is useful when + * authStatus is SAMLRedirect/OIDCRedirect and inPopup is set to true + */ + TRIGGER_SSO_POPUP = "TRIGGER_SSO_POPUP" +} +/** + * + */ +export declare function getAuthEE(): EventEmitter; +/** + * + * @param eventEmitter + */ +export declare function setAuthEE(eventEmitter: EventEmitter): void; +/** + * + */ +export declare function notifyAuthSDKSuccess(): void; +/** + * + */ +export declare function notifyAuthSuccess(): Promise; +/** + * + * @param failureType + */ +export declare function notifyAuthFailure(failureType: AuthFailureType): void; +/** + * + */ +export declare function notifyLogout(): void; +/** + * Services to be called after the login is successful, + * This should be called after the cookie is set for cookie auth or + * after the token is set for cookieless. + * @return {Promise} + * @example + * ```js + * await postLoginService(); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + */ +export declare function postLoginService(): Promise; +/** + * Return releaseVersion if available + */ +export declare function getReleaseVersion(): string; +/** + * Perform token based authentication + * @param embedConfig The embed configuration + */ +export declare const doTokenAuth: (embedConfig: EmbedConfig) => Promise; +/** + * Validate embedConfig parameters required for cookielessTokenAuth + * @param embedConfig The embed configuration + */ +export declare const doCookielessTokenAuth: (embedConfig: EmbedConfig) => Promise; +/** + * Perform basic authentication to the ThoughtSpot cluster using the cluster + * credentials. + * + * Warning: This feature is primarily intended for developer testing. It is + * strongly advised not to use this authentication method in production. + * @param embedConfig The embed configuration + */ +export declare const doBasicAuth: (embedConfig: EmbedConfig) => Promise; +export declare const doSamlAuth: (embedConfig: EmbedConfig) => Promise; +export declare const doOIDCAuth: (embedConfig: EmbedConfig) => Promise; +export declare const logout: (embedConfig: EmbedConfig) => Promise; +/** + * Perform authentication on the ThoughtSpot cluster + * @param embedConfig The embed configuration + */ +export declare const authenticate: (embedConfig: EmbedConfig) => Promise; +/** + * Check if we are authenticated to the ThoughtSpot cluster + */ +export declare const isAuthenticated: () => boolean; +//# sourceMappingURL=auth.d.ts.map \ No newline at end of file diff --git a/dist/src/auth.d.ts.map b/dist/src/auth.d.ts.map new file mode 100644 index 00000000..6faef0be --- /dev/null +++ b/dist/src/auth.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["auth.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAC;AAIzC,OAAO,EACoB,WAAW,EACrC,MAAM,SAAS,CAAC;AAgBjB,eAAO,IAAI,cAAc,SAAQ,CAAC;AAElC,eAAO,IAAI,cAAc,EAAE,MAAa,CAAC;AAEzC,eAAO,IAAI,qBAAqB,EAAE,OAAO,CAAC,IAAI,CAAQ,CAAC;AAIvD,eAAO,MAAM,2BAA2B,yCAAyC,CAAC;AAElF;;;;GAIG;AACH,oBAAY,eAAe;IACvB;;;;;;OAMG;IACH,GAAG,QAAQ;IACX;;;;;OAKG;IACH,gBAAgB,qBAAqB;IACrC;;;;;OAKG;IACH,MAAM,WAAW;IACjB;;;;OAIG;IACH,KAAK,UAAU;IACf;;;;OAIG;IACH,oBAAoB,yBAAyB;IAC7C;;;;OAIG;IACH,uBAAuB,4BAA4B;CACtD;AAED;;;GAGG;AACH,oBAAY,UAAU;IAClB;;OAEG;IACH,OAAO,YAAY;IACnB;;;;;;;;;;;;;;OAcG;IACH,WAAW,gBAAgB;IAC3B;;;;OAIG;IACH,oBAAoB,yBAAyB;IAC7C;;;;;;;;;;;;;;OAcG;IACH,OAAO,YAAY;IACnB;;OAEG;IACH,MAAM,WAAW;IACjB;;;;;OAKG;IACH,iBAAiB,sBAAsB;IAEvC;;OAEG;IACH,yBAAyB,8BAA8B;CAC1D;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC7B;;;;OAIG;IACH,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC,WAAW,EAAE,eAAe,KAAK,IAAI,GAAG,IAAI,CAAC;IACtF;;;;OAIG;IACH,EAAE,CACE,KAAK,EAAE,UAAU,CAAC,WAAW,GAAG,UAAU,CAAC,MAAM,GAAG,UAAU,CAAC,iBAAiB,GAAG,UAAU,CAAC,yBAAyB,EACvH,QAAQ,EAAE,MAAM,IAAI,GACrB,IAAI,CAAC;IACR,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC,WAAW,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI,CAAC;IAC1E,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC,WAAW,EAAE,eAAe,KAAK,IAAI,GAAG,IAAI,CAAC;IACxF,IAAI,CACA,KAAK,EAAE,UAAU,CAAC,WAAW,GAAG,UAAU,CAAC,MAAM,GAAG,UAAU,CAAC,iBAAiB,GAAG,UAAU,CAAC,yBAAyB,EACvH,QAAQ,EAAE,MAAM,IAAI,GACrB,IAAI,CAAC;IACR,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC,WAAW,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI,CAAC;IAC5E;;;OAGG;IACH,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC;IAChD;;;;;;OAMG;IACH,GAAG,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAC9F;;;OAGG;IACH,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;CAC/C;AAED;;;GAGG;AACH,oBAAY,SAAS;IACjB;;;OAGG;IACH,iBAAiB,sBAAsB;CAC1C;AAID;;GAEG;AACH,wBAAgB,SAAS,IAAI,YAAY,CAAC,UAAU,GAAG,SAAS,CAAC,CAEhE;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,YAAY,EAAE,YAAY,CAAC,UAAU,GAAG,SAAS,CAAC,GAAG,IAAI,CAElF;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAM3C;AAED;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAYvD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,eAAe,GAAG,IAAI,CAMpE;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,IAAI,CAMnC;AAeD;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAYtD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,WAEhC;AAyBD;;;GAGG;AACH,eAAO,MAAM,WAAW,gBAAuB,WAAW,KAAG,QAAQ,OAAO,CAgC3E,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,gBAAuB,WAAW,KAAG,QAAQ,OAAO,CAgBrF,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,gBAAuB,WAAW,KAAG,QAAQ,OAAO,CAa3E,CAAC;AAoGF,eAAO,MAAM,UAAU,gBAAuB,WAAW,qBAiBxD,CAAC;AAEF,eAAO,MAAM,UAAU,gBAAuB,WAAW,qBAkBxD,CAAC;AAEF,eAAO,MAAM,MAAM,gBAAuB,WAAW,KAAG,QAAQ,OAAO,CAYtE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,YAAY,gBAAuB,WAAW,KAAG,QAAQ,OAAO,CAoB5E,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,QAAO,OAAyB,CAAC"} \ No newline at end of file diff --git a/dist/src/auth.spec.d.ts b/dist/src/auth.spec.d.ts new file mode 100644 index 00000000..a3f4727a --- /dev/null +++ b/dist/src/auth.spec.d.ts @@ -0,0 +1,15 @@ +import 'jest-fetch-mock'; +export declare const embedConfig: any; +export declare const mockSessionInfoApiResponse: { + userGUID: string; + releaseVersion: string; + configInfo: { + isPublicUser: boolean; + mixpanelConfig: { + production: boolean; + devSdkKey: string; + prodSdkKey: string; + }; + }; +}; +//# sourceMappingURL=auth.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/auth.spec.d.ts.map b/dist/src/auth.spec.d.ts.map new file mode 100644 index 00000000..a28aa8d6 --- /dev/null +++ b/dist/src/auth.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"auth.spec.d.ts","sourceRoot":"","sources":["auth.spec.ts"],"names":[],"mappings":"AAAA,OAAO,iBAAiB,CAAC;AAkBzB,eAAO,MAAM,WAAW,EAAE,GAoFzB,CAAC;AAIF,eAAO,MAAM,0BAA0B;;;;;;;;;;;CAWtC,CAAC"} \ No newline at end of file diff --git a/dist/src/authToken.d.ts b/dist/src/authToken.d.ts new file mode 100644 index 00000000..61bb9b56 --- /dev/null +++ b/dist/src/authToken.d.ts @@ -0,0 +1,20 @@ +import { EmbedConfig } from './types'; +export declare const getCacheAuthToken: () => string | null; +export declare const storeAuthTokenInCache: (token: string) => void; +/** + * + * @param embedConfig + */ +export declare function getAuthenticationToken(embedConfig: EmbedConfig, skipvalidation?: boolean): Promise; +export declare const validateAuthToken: (embedConfig: EmbedConfig, authToken: string, suppressAlert?: boolean) => Promise; +/** + * Resets the auth token and a new token will be fetched on the next request. + * @example + * ```js + * resetCachedAuthToken(); + * ``` + * @version SDK: 1.28.0 | ThoughtSpot: * + * @group Authentication / Init + */ +export declare const resetCachedAuthToken: () => void; +//# sourceMappingURL=authToken.d.ts.map \ No newline at end of file diff --git a/dist/src/authToken.d.ts.map b/dist/src/authToken.d.ts.map new file mode 100644 index 00000000..73e91b46 --- /dev/null +++ b/dist/src/authToken.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"authToken.d.ts","sourceRoot":"","sources":["authToken.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAOtC,eAAO,MAAM,iBAAiB,QAAO,MAAM,GAAG,IAA6C,CAAC;AAC5F,eAAO,MAAM,qBAAqB,UAAW,MAAM,KAAG,IAErD,CAAC;AAGF;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,WAAW,EAAE,WAAW,EAAE,cAAc,GAAE,OAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAmCvH;AAED,eAAO,MAAM,iBAAiB,gBACb,WAAW,aACb,MAAM,kBACD,OAAO,KACxB,QAAQ,OAAO,CAgCjB,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,QAAO,IAEvC,CAAC"} \ No newline at end of file diff --git a/dist/src/authToken.spec.d.ts b/dist/src/authToken.spec.d.ts new file mode 100644 index 00000000..42653f05 --- /dev/null +++ b/dist/src/authToken.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=authToken.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/authToken.spec.d.ts.map b/dist/src/authToken.spec.d.ts.map new file mode 100644 index 00000000..c7cbc4d7 --- /dev/null +++ b/dist/src/authToken.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"authToken.spec.d.ts","sourceRoot":"","sources":["authToken.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/config.d.ts b/dist/src/config.d.ts new file mode 100644 index 00000000..507baef6 --- /dev/null +++ b/dist/src/config.d.ts @@ -0,0 +1,21 @@ +import { EmbedConfig } from './types'; +/** + * Parse and construct the ThoughtSpot hostname or IP address + * from the embed configuration object. + * @param config + */ +export declare const getThoughtSpotHost: (config: EmbedConfig) => string; +export declare const getV2BasePath: (config: EmbedConfig) => string; +/** + * It is a good idea to keep URLs under 2000 chars. + * If this is ever breached, since we pass view configuration through + * URL params, we would like to log a warning. + * Reference: https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers + */ +export declare const URL_MAX_LENGTH = 2000; +/** + * The default CSS dimensions of the embedded app + */ +export declare const DEFAULT_EMBED_WIDTH = "100%"; +export declare const DEFAULT_EMBED_HEIGHT = "100%"; +//# sourceMappingURL=config.d.ts.map \ No newline at end of file diff --git a/dist/src/config.d.ts.map b/dist/src/config.d.ts.map new file mode 100644 index 00000000..81d25268 --- /dev/null +++ b/dist/src/config.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["config.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAoBtC;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,WAAY,WAAW,KAAG,MAmBxD,CAAC;AAEF,eAAO,MAAM,aAAa,WAAY,WAAW,KAAG,MAgBnD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,OAAO,CAAC;AAEnC;;GAEG;AACH,eAAO,MAAM,mBAAmB,SAAS,CAAC;AAC1C,eAAO,MAAM,oBAAoB,SAAS,CAAC"} \ No newline at end of file diff --git a/dist/src/config.spec.d.ts b/dist/src/config.spec.d.ts new file mode 100644 index 00000000..117f231e --- /dev/null +++ b/dist/src/config.spec.d.ts @@ -0,0 +1,5 @@ +/** + * @jest-environment node + */ +export {}; +//# sourceMappingURL=config.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/config.spec.d.ts.map b/dist/src/config.spec.d.ts.map new file mode 100644 index 00000000..0156b505 --- /dev/null +++ b/dist/src/config.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"config.spec.d.ts","sourceRoot":"","sources":["config.spec.ts"],"names":[],"mappings":"AAAA;;GAEG"} \ No newline at end of file diff --git a/dist/src/css-variables.d.ts b/dist/src/css-variables.d.ts new file mode 100644 index 00000000..9bb3f1e3 --- /dev/null +++ b/dist/src/css-variables.d.ts @@ -0,0 +1,961 @@ +/** + * The list of customization css variables. These + * are the only allowed variables possible. + */ +export interface CustomCssVariables { + /** + * Background color of the Liveboard, visualization, Search, and Answer pages. + */ + '--ts-var-root-background'?: string; + /** + * Color of the text on application pages. + */ + '--ts-var-root-color'?: string; + /** + * Font type for the text on application pages. + */ + '--ts-var-root-font-family'?: string; + /** + * Text transformation specification for UI elements in the app. + */ + '--ts-var-root-text-transform'?: string; + /** + * Font color of the text on toggle buttons such as + * **All**, **Answers**, and **Liveboards** on the Home page (Classic experience), + * the text color of the chart and table tiles on Home page (New modular Homepage + * experience), title text on the AI-generated charts and tables, and object titles on + * list pages such as *Liveboards* and *Answers*. + * The default color code is #2770EF. + * + */ + '--ts-var-application-color'?: string; + /** + * Background color of the top navigation panel. + */ + '--ts-var-nav-background'?: string; + /** + * Font color of the top navigation panel. + */ + '--ts-var-nav-color'?: string; + /** + * Background color of the *Search data* button. + */ + '--ts-var-search-data-button-background'?: string; + /** + * Color of the text on the *Search data* button. + */ + '--ts-var-search-data-button-font-color'?: string; + /** + * Font of the text on the *Search data* button. + */ + '--ts-var-search-data-button-font-family'?: string; + /** + * Font color of the text in the Search bar. + */ + '--ts-var-search-bar-text-font-color'?: string; + /** + * Font of the text in the Search bar. + */ + '--ts-var-search-bar-text-font-family'?: string; + /** + * Font style of the text in the Search bar. + */ + '--ts-var-search-bar-text-font-style'?: string; + /** + * Background color of the search bar. + */ + '--ts-var-search-bar-background'?: string; + /** + * Background color of the search suggestions panel. + */ + '--ts-var-search-auto-complete-background'?: string; + /** + * Background color of the navigation panel that allows you to undo, redo, and reset + * search operations. + */ + '--ts-var-search-navigation-button-background'?: string; + /** + * Background color of the navigation help text that appears at the bottom of the + * search suggestions panel. + */ + '--ts-var-search-bar-navigation-help-text-background'?: string; + /** + * Background color of the search suggestion block on hover. + */ + '--ts-var-search-bar-auto-complete-hover-background'?: string; + /** + * Font color of the text in the search suggestion panel. + */ + '--ts-var-search-auto-complete-font-color'?: string; + /** + * Font color of the sub-text that appears below the keyword in the search suggestion + * panel. + */ + '--ts-var-search-auto-complete-subtext-font-color'?: string; + /** + * Background color of the input box in the Spotter page. + */ + '--ts-var-spotter-input-background'?: string; + /** + * Background color of the previously asked prompt message in the Spotter page. + */ + '--ts-var-spotter-prompt-background'?: string; + /** + * Background color of the data panel. + */ + '--ts-var-answer-data-panel-background-color'?: string; + /** + * Background color of the vertical panel on the right side of the Answer page, which + * includes the options to edit charts and tables. + */ + '--ts-var-answer-edit-panel-background-color'?: string; + /** + * Background color of the chart switcher on search results and Answer pages. + */ + '--ts-var-answer-view-table-chart-switcher-background'?: string; + /** + * Background color of the currently selected chart type in the chart switcher. + */ + '--ts-var-answer-view-table-chart-switcher-active-background'?: string; + /** + * Border-radius of main buttons. + * For example, the *Search data* button in the top navigation panel. + */ + '--ts-var-button-border-radius'?: string; + /** + * Border-radius of small buttons such as secondary buttons. + * For example, share and favorite buttons. + */ + '--ts-var-button--icon-border-radius'?: string; + /** + * Font color of the text on primary buttons. For example, the primary buttons on + * Liveboard*, Answer, *Data* workspace, *SpotIQ*, or *Home* page. + */ + '--ts-var-button--primary-color'?: string; + /** + * Font family specification for the text on primary buttons. + */ + '--ts-var-button--primary--font-family'?: string; + /** + * Background color of the primary buttons. For example, the primary buttons such as + * Pin* and *Save*. + */ + '--ts-var-button--primary-background'?: string; + /** + * Background color of the primary buttons on hover. + */ + '--ts-var-button--primary--hover-background'?: string; + /** + * Backgroud color of the primary buttons when active. + */ + '--ts-var-button--primary--active-background'?: string; + /** + * Font color of the text on the secondary buttons. + */ + '--ts-var-button--secondary-color'?: string; + /** + * Font family specification for the text on the secondary buttons. + */ + '--ts-var-button--secondary--font-family'?: string; + /** + * Background color of the secondary buttons. + */ + '--ts-var-button--secondary-background'?: string; + /** + * Background color of the secondary button on hover. + */ + '--ts-var-button--secondary--hover-background'?: string; + /** + * Backgroud color of the secondary buttons when active. + */ + '--ts-var-button--secondary--active-background'?: string; + /** + * Font color of the tertiary button. For example, the *Undo*, *Redo*, and *Reset* + * buttons on the *Search* page. + */ + '--ts-var-button--tertiary-color'?: string; + /** + * Background color of the tertiary button. + */ + '--ts-var-button--tertiary-background'?: string; + /** + * Background color of the tertiary button when a user hovers over these buttons. + */ + '--ts-var-button--tertiary--hover-background'?: string; + /** + * Backgroud color of the tertiary buttons when active. + */ + '--ts-var-button--tertiary--active-background'?: string; + /** + * Font color of the title text of a visualization or Answer. + */ + '--ts-var-viz-title-color'?: string; + /** + * Font family specification for the title text of a visualization/Answer. + */ + '--ts-var-viz-title-font-family'?: string; + /** + * Text transformation specification for visualization and Answer titles. + */ + '--ts-var-viz-title-text-transform'?: string; + /** + * Font color of the description text and subtitle of a visualization or Answer. + */ + '--ts-var-viz-description-color'?: string; + /** + * Font family specification of description text and subtitle of a visualization or + * Answer. + */ + '--ts-var-viz-description-font-family'?: string; + /** + * Text transformation specification for description text and subtitle of a + * visualization or Answer. + */ + '--ts-var-viz-description-text-transform'?: string; + /** + * Border-radius for the visualization tiles and header panel on a Liveboard. + */ + '--ts-var-viz-border-radius'?: string; + /** + * Box shadow property for the visualization tiles and header panel on a Liveboard. + */ + '--ts-var-viz-box-shadow'?: string; + /** + * Background color of the visualization tiles and header panel on a Liveboard. + */ + '--ts-var-viz-background'?: string; + /** + * Background color of the legend on a visualization or Answer. + */ + '--ts-var-viz-legend-hover-background'?: string; + /** + * Background color of the selected chart type on the chart selection widget. + */ + '--ts-var-answer-chart-select-background'?: string; + /** + * Background color of the chart type element when a user hovers over a chart type on + * the chart selection widget. + */ + '--ts-var-answer-chart-hover-background'?: string; + /** + * Border-radius of filter chips. + */ + '--ts-var-chip-border-radius'?: string; + /** + * Shadow effect for filter chips. + */ + '--ts-var-chip-box-shadow'?: string; + /** + * Background color of filter chips. + */ + '--ts-var-chip-background'?: string; + /** + * Font color of the filter label when a filter chip is selected + */ + '--ts-var-chip--active-color'?: string; + /** + * Background color of the filter chips when selected. + */ + '--ts-var-chip--active-background'?: string; + /** + * Font color of the text on filter chips when hovered over. + */ + '--ts-var-chip--hover-color'?: string; + /** + * Background color of filter chips on hover. + */ + '--ts-var-chip--hover-background'?: string; + /** + * Font color of the text on filter chips. + */ + '--ts-var-chip-color'?: string; + /** + * Font family specification for the text on filter chips. + */ + '--ts-var-chip-title-font-family'?: string; + /** + * Font color of axis title on charts. + */ + '--ts-var-axis-title-color'?: string; + /** + * Font family specification for the X and Y axis title text. + */ + '--ts-var-axis-title-font-family'?: string; + /** + * Font color of the X and Y axis labels. + */ + '--ts-var-axis-data-label-color'?: string; + /** + * Font family specification for X and Y axis labels. + */ + '--ts-var-axis-data-label-font-family'?: string; + /** + * Font color of the menu items. + */ + '--ts-var-menu-color'?: string; + /** + * Background color of menu panels. + */ + '--ts-var-menu-background'?: string; + /** + * Font family specification for the menu items. + */ + '--ts-var-menu-font-family'?: string; + /** + * Text capitalization specification for the menu items. + */ + '--ts-var-menu-text-transform'?: string; + /** + * Background color for menu items on hover. + */ + '--ts-var-menu--hover-background'?: string; + /** + * Text color for selected menu items. + */ + '--ts-var-menu-selected-text-color'?: string; + /** + * Background color of the dialogs. + */ + '--ts-var-dialog-body-background'?: string; + /** + * Font color of the body text displayed on dialogs. + */ + '--ts-var-dialog-body-color'?: string; + /** + * Background color of the header text on dialogs. + */ + '--ts-var-dialog-header-background'?: string; + /** + * Font color of the header text on dialogs. + */ + '--ts-var-dialog-header-color'?: string; + /** + * Background color of the footer area on dialogs. + */ + '--ts-var-dialog-footer-background'?: string; + /** + * Background for selected state in list + */ + '--ts-var-list-selected-background'?: string; + /** + * Background for hover state in list + */ + '--ts-var-list-hover-background'?: string; + /** + * Background for hover state in segment control. + */ + '--ts-var-segment-control-hover-background'?: string; + /** + * Text color for slected item in modular home's watchlist. + */ + '--ts-var-home-watchlist-selected-text-color'?: string; + /** + * Text color for favorite carousel find your favorites card in modular home. + */ + '--ts-var-home-favorite-suggestion-card-text-color'?: string; + /** + * Icon color for favorite carousel find your favorites card in modular home. + */ + '--ts-var-home-favorite-suggestion-card-icon-color'?: string; + /** + * Background for favorite carousel find your favorites card in modular home. + */ + '--ts-var-home-favorite-suggestion-card-background'?: string; + /** + * Border color of checkbox in error state. + */ + '--ts-var-checkbox-error-border'?: string; + /** + * Border color of checkbox. + */ + '--ts-var-checkbox-border-color'?: string; + /** + * Border color of checkbox in hover state. + */ + '--ts-var-checkbox-hover-border'?: string; + /** + * Border and font color of checkbox in active state. + */ + '--ts-var-checkbox-active-color'?: string; + /** + * Border color and font color of checkbox in checked state. + */ + '--ts-var-checkbox-checked-color'?: string; + /** + * Border and font color of checkbox in disabled state. + */ + '--ts-var-checkbox-checked-disabled'?: string; + /** + * Background color of checkbox. + */ + '--ts-var-checkbox-background-color'?: string; + /** + * Background color of the layout in the Liveboard. + */ + '--ts-var-liveboard-layout-background'?: string; + /** + * Background color of the header in the Liveboard. + */ + '--ts-var-liveboard-header-background'?: string; + /** + * Font color of the header in the Liveboard. + */ + '--ts-var-liveboard-header-font-color'?: string; + /** + * Border color of the tiles in the Liveboard. + */ + '--ts-var-liveboard-tile-border-color'?: string; + /** + * Background color of the tiles in the Liveboard. + */ + '--ts-var-liveboard-tile-background'?: string; + /** + * Border radius of the tiles in the Liveboard. + */ + '--ts-var-liveboard-tile-border-radius'?: string; + /** + * Padding of the tiles in the Liveboard. + */ + '--ts-var-liveboard-tile-padding'?: string; + /** + * Background color of the table header in the Liveboard. + */ + '--ts-var-liveboard-tile-table-header-background'?: string; + /** + * Padding of the groups in the Liveboard. + * + * Please enable the Liveboard Masterpieces feature in your ThoughtSpot instance and + * then set the isLiveboardMasterpiecesEnabled SDK flag to true to start modifying + * this CSS variable. + */ + '--ts-var-liveboard-group-padding'?: string; + /** + * Font size of the title of the groups in the Liveboard. + * + * Please enable the Liveboard Masterpieces feature in your ThoughtSpot instance and + * then set the isLiveboardMasterpiecesEnabled SDK flag to true to start modifying + * this CSS variable. + */ + '--ts-var-liveboard-group-title-font-size'?: string; + /** + * Font weight of the title of the groups in the Liveboard. + * + * Please enable the Liveboard Masterpieces feature in your ThoughtSpot instance and + * then set the isLiveboardMasterpiecesEnabled SDK flag to true to start modifying + * this CSS variable. + */ + '--ts-var-liveboard-group-title-font-weight'?: string; + /** + * Font size of the title of the tiles inside the groups in the Liveboard. + * + * Please enable the Liveboard Masterpieces feature in your ThoughtSpot instance and + * then set the isLiveboardMasterpiecesEnabled SDK flag to true to start modifying + * this CSS variable. + */ + '--ts-var-liveboard-group-tile-title-font-size'?: string; + /** + * Font weight of the title of the tiles inside the groups in the Liveboard. + * + * Please enable the Liveboard Masterpieces feature in your ThoughtSpot instance and + * then set the isLiveboardMasterpiecesEnabled SDK flag to true to start modifying + * this CSS variable. + */ + '--ts-var-liveboard-group-tile-title-font-weight'?: string; + /** + * Padding of the group tiles in the Liveboard. + * + * Please enable the Liveboard Masterpieces feature in your ThoughtSpot instance and + * then set the isLiveboardMasterpiecesEnabled SDK flag to true to start modifying + * this CSS variable. + */ + '--ts-var-liveboard-group-tile-padding'?: string; + /** + * Padding of the answer viz in the Liveboard. + */ + '--ts-var-liveboard-answer-viz-padding'?: string; + /** + * Background color of the groups in the Liveboard. + * + * Please enable the Liveboard Masterpieces feature in your ThoughtSpot instance and + * then set the isLiveboardMasterpiecesEnabled SDK flag to true to start modifying + * this CSS variable. + */ + '--ts-var-liveboard-group-background'?: string; + /** + * Border color of the groups in the Liveboard. + * + * Please enable the Liveboard Masterpieces feature in your ThoughtSpot instance and + * then set the isLiveboardMasterpiecesEnabled SDK flag to true to start modifying + * this CSS variable. + */ + '--ts-var-liveboard-group-border-color'?: string; + /** + * Font color of the heading of the note title in the Liveboard. + */ + '--ts-var-liveboard-notetitle-heading-font-color'?: string; + /** + * Font color of the body of the note title in the Liveboard. + */ + '--ts-var-liveboard-notetitle-body-font-color'?: string; + /** + * Font color of the title of the groups in the Liveboard. + * + * Please enable the Liveboard Masterpieces feature in your ThoughtSpot instance and + * then set the isLiveboardMasterpiecesEnabled SDK flag to true to start modifying + * this CSS variable. + */ + '--ts-var-liveboard-group-title-font-color'?: string; + /** + * Font color of the description of the groups in the Liveboard. + * + * Please enable the Liveboard Masterpieces feature in your ThoughtSpot instance and + * then set the isLiveboardMasterpiecesEnabled SDK flag to true to start modifying + * this CSS variable. + */ + '--ts-var-liveboard-group-description-font-color'?: string; + /** + * Font color of the title of the tiles inside the groups in the Liveboard. + * + * Please enable the Liveboard Masterpieces feature in your ThoughtSpot instance and + * then set the isLiveboardMasterpiecesEnabled SDK flag to true to start modifying + * this CSS variable. + */ + '--ts-var-liveboard-group-tile-title-font-color'?: string; + /** + * Font color of the description of the tiles inside the groups in the Liveboard. + * + * Please enable the Liveboard Masterpieces feature in your ThoughtSpot instance and + * then set the isLiveboardMasterpiecesEnabled SDK flag to true to start modifying + * this CSS variable. + */ + '--ts-var-liveboard-group-tile-description-font-color'?: string; + /** + * Background color of the tiles inside the groups in the Liveboard. + * + * Please enable the Liveboard Masterpieces feature in your ThoughtSpot instance and + * then set the isLiveboardMasterpiecesEnabled SDK flag to true to start modifying + * this CSS variable. + */ + '--ts-var-liveboard-group-tile-background'?: string; + /** + * Background color of the filter chips in the Liveboard. + * + * Please enable the Liveboard Masterpieces feature in your ThoughtSpot instance and + * then set the isLiveboardMasterpiecesEnabled SDK flag to true to start modifying + * this CSS variable. + */ + '--ts-var-liveboard-chip-background'?: string; + /** + * Font color of the filter chips in the Liveboard. + * + * Please enable the Liveboard Masterpieces feature in your ThoughtSpot instance and + * then set the isLiveboardMasterpiecesEnabled SDK flag to true to start modifying + * this CSS variable. + */ + '--ts-var-liveboard-chip-color'?: string; + /** + * Background color of the filter chips in the Liveboard on hover. + * + * Please enable the Liveboard Masterpieces feature in your ThoughtSpot instance and + * then set the isLiveboardMasterpiecesEnabled SDK flag to true to start modifying + * this CSS variable. + */ + '--ts-var-liveboard-chip--hover-background'?: string; + /** + * Background color of the filter chips in the Liveboard on active. + * + * Please enable the Liveboard Masterpieces feature in your ThoughtSpot instance and + * then set the isLiveboardMasterpiecesEnabled SDK flag to true to start modifying + * this CSS variable. + */ + '--ts-var-liveboard-chip--active-background'?: string; + /** + * Width of the side panel in the Liveboard. + */ + '--ts-var-side-panel-width'?: string; + /** + * Background color of the edit bar in the Liveboard. + */ + '--ts-var-liveboard-edit-bar-background'?: string; + /** + * Breakpoint for the dual column layout in the Liveboard. + */ + '--ts-var-liveboard-dual-column-breakpoint'?: string; + /** + * Breakpoint for the single column layout in the Liveboard. + */ + '--ts-var-liveboard-single-column-breakpoint'?: string; + /** + * Background color of the cross filter layout in the Liveboard. + */ + '--ts-var-liveboard-cross-filter-layout-background'?: string; + /** + * Border color of the active tab in the Liveboard. + */ + '--ts-var-liveboard-tab-active-border-color'?: string; + /** + * Font color of the hover tab in the Liveboard. + */ + '--ts-var-liveboard-tab-hover-color'?: string; + /** + * Font size of the title of the tiles in the Liveboard. + */ + '--ts-var-liveboard-tile-title-fontsize'?: string; + /** + * Font weight of the title of the tiles in the Liveboard. + */ + '--ts-var-liveboard-tile-title-fontweight'?: string; + /** + * Background color of the parameter chips in the Liveboard. + */ + '--ts-var-parameter-chip-background'?: string; + /** + * Font color of the parameter chips in the Liveboard. + */ + '--ts-var-parameter-chip-text-color'?: string; + /** + * Background color of the parameter chips in the Liveboard on hover. + */ + '--ts-var-parameter-chip-hover-background'?: string; + /** + * Font color of the parameter chips in the Liveboard on hover. + */ + '--ts-var-parameter-chip-hover-text-color'?: string; + /** + * Background color of the parameter chips in the Liveboard on active. + */ + '--ts-var-parameter-chip-active-background'?: string; + /** + * Font color of the parameter chips in the Liveboard on active. + */ + '--ts-var-parameter-chip-active-text-color'?: string; + /** + * Background color of the action button in the Liveboard header. + */ + '--ts-var-liveboard-header-action-button-background'?: string; + /** + * Font color of the action button in the Liveboard header. + */ + '--ts-var-liveboard-header-action-button-font-color'?: string; + /** + * Font color of the action button in the Liveboard header on hover. + */ + '--ts-var-liveboard-header-action-button-hover-color'?: string; + /** + * Font color of the action button in the Liveboard header on active. + */ + '--ts-var-liveboard-header-action-button-active-color'?: string; + /** + * Background color of the badge in the Liveboard header. + */ + '--ts-var-liveboard-header-badge-background'?: string; + /** + * Font color of the badge in the Liveboard header. + */ + '--ts-var-liveboard-header-badge-font-color'?: string; + /** + * Background color of the modified badge in the Liveboard header. + */ + '--ts-var-liveboard-header-badge-modified-background'?: string; + /** + * Font color of the modified badge in the Liveboard header. + */ + '--ts-var-liveboard-header-badge-modified-font-color'?: string; + /** + * Font color of the badge in the Liveboard header on hover. + */ + '--ts-var-liveboard-header-badge-hover-color'?: string; + /** + * Font color of the badge in the Liveboard header on active. + */ + '--ts-var-liveboard-header-badge-active-color'?: string; + /** + * Font color of the hero text in the KPI widget. + */ + '--ts-var-kpi-hero-color'?: string; + /** + * Font color of the comparison text in the KPI widget. + */ + '--ts-var-kpi-comparison-color'?: string; + /** + * Font color of the analyze text in the KPI widget. + */ + '--ts-var-kpi-analyze-text-color'?: string; + /** + * Font color of the legend title in the heatmap chart. + */ + '--ts-var-chart-heatmap-legend-title-color'?: string; + /** + * Font color of the legend label in the heatmap chart. + */ + '--ts-var-chart-heatmap-legend-label-color'?: string; + /** + * Font color of the legend title in the treemap chart. + */ + '--ts-var-chart-treemap-legend-title-color'?: string; + /** + * Font color of the legend label in the treemap chart. + */ + '--ts-var-chart-treemap-legend-label-color'?: string; + /** + * Color of the positive change in the KPI. + */ + '--ts-var-kpi-positive-change-color'?: string; + /** + * Color of the negative change in the KPI. + */ + '--ts-var-kpi-negative-change-color'?: string; + /** + * Background color of the change analysis insights. + */ + '--ts-var-change-analysis-insights-background'?: string; + /** + * Background color of the forecasting card in the SpotIQ analyze. + */ + '--ts-var-spotiq-analyze-forecasting-card-background'?: string; + /** + * Background color of the outlier card in the SpotIQ analyze. + */ + '--ts-var-spotiq-analyze-outlier-card-background'?: string; + /** + * Background color of the trend card in the SpotIQ analyze. + */ + '--ts-var-spotiq-analyze-trend-card-background'?: string; + /** + * Background color of the crosscorrelation card in the SpotIQ analyze. + */ + '--ts-var-spotiq-analyze-crosscorrelation-card-background'?: string; + /** + * Background color of the summary header in the CCA modal. + */ + '--ts-var-cca-modal-summary-header-background'?: string; + /** + * Width of the Spotter chat window. + */ + '--ts-var-spotter-chat-width'?: string; + /** + * Border color for the saved chats sidebar container. + */ + '--ts-var-saved-chats-border-color'?: string; + /** + * Background color for the saved chats sidebar container. + */ + '--ts-var-saved-chats-bg'?: string; + /** + * Text color for the saved chats sidebar container. + */ + '--ts-var-saved-chats-text-color'?: string; + /** + * Border color for the saved chats sidebar header. + */ + '--ts-var-saved-chats-header-border'?: string; + /** + * Color for the saved chats sidebar title text. + */ + '--ts-var-saved-chats-title-color'?: string; + /** + * Background color for buttons (new chat, toggle, footer) in the saved chats sidebar. + */ + '--ts-var-saved-chats-btn-bg'?: string; + /** + * Hover background color for buttons in the saved chats sidebar. + */ + '--ts-var-saved-chats-btn-hover-bg'?: string; + /** + * Text color for conversation items in the saved chats sidebar. + */ + '--ts-var-saved-chats-conv-text-color'?: string; + /** + * Background color for conversation items on hover in the saved chats sidebar. + */ + '--ts-var-saved-chats-conv-hover-bg'?: string; + /** + * Background color for the active/selected conversation in the saved chats sidebar. + */ + '--ts-var-saved-chats-conv-active-bg'?: string; + /** + * Border color for the saved chats sidebar footer. + */ + '--ts-var-saved-chats-footer-border'?: string; + /** + * Color for section title text (e.g., "Recent", "Older") in the saved chats sidebar. + */ + '--ts-var-saved-chats-section-title-color'?: string; + /** + * Text color of the styling panel in the Liveboard. + */ + '--ts-var-liveboard-styling-panel-text-color'?: string; + /** + * Border color of the styling panel in the Liveboard. + */ + '--ts-var-liveboard-styling-panel-border-color'?: string; + /** + * Background color of the styling button in the Liveboard. + */ + '--ts-var-liveboard-styling-button-background'?: string; + /** + * Text color of the styling button in the Liveboard. + */ + '--ts-var-liveboard-styling-button-text-color'?: string; + /** + * Background color of the styling button in the Liveboard on hover. + */ + '--ts-var-liveboard-styling-button-hover-background'?: string; + /** + * Background color of the styling button in the Liveboard when active. + */ + '--ts-var-liveboard-styling-button-active-background'?: string; + /** + * Text color of the styling button in the Liveboard on hover. + */ + '--ts-var-liveboard-styling-button-hover-text-color'?: string; + /** + * Box shadow of the styling button in the Liveboard. + */ + '--ts-var-liveboard-styling-button-shadow'?: string; + /** + * Background color of the color palette in the Liveboard styling panel. + */ + '--ts-var-liveboard-styling-color-palette-background'?: string; + /** + * Main panel background of the SpotterViz. + */ + '--ts-var-spotterviz-panel-background'?: string; + /** + * Background color of the chat input field in SpotterViz. + */ + '--ts-var-spotterviz-input-background'?: string; + /** + * Placeholder text color of the chat input field in SpotterViz. + */ + '--ts-var-spotterviz-input-placeholder-color'?: string; + /** + * CTA color of the chat input field in SpotterViz. + */ + '--ts-var-spotterviz-input-cta-color'?: string; + /** + * CTA hover color of the chat input field in SpotterViz. + */ + '--ts-var-spotterviz-input-cta-hover-color'?: string; + /** + * Text color for the SpotterViz label in the empty state, displayed below + * the SpotterViz icon. + */ + '--ts-var-spotterviz-emptystate-spotterviz-color'?: string; + /** + * Background color of the starter prompt cards in SpotterViz. + */ + '--ts-var-spotterviz-prompt-card-background'?: string; + /** + * Background hover color of the starter prompt cards in SpotterViz. + */ + '--ts-var-spotterviz-prompt-card-hover-background'?: string; + /** + * Primary text color in SpotterViz, also used for tool response text, + * upvote/downvote buttons, and other primary content. + */ + '--ts-var-spotterviz-text-primary'?: string; + /** + * Secondary text color in SpotterViz. + */ + '--ts-var-spotterviz-text-secondary'?: string; + /** + * Background color of the user chat message bubble in SpotterViz. + */ + '--ts-var-spotterviz-message-background'?: string; + /** + * Hover color for the user chat message expand button in SpotterViz. + */ + '--ts-var-spotterviz-usermessage-icon-hover'?: string; + /** + * Background color of the user chat message expand button in SpotterViz. + */ + '--ts-var-spotterviz-usermessage-icon-background'?: string; + /** + * Color of the thinking step header when in progress in SpotterViz. + */ + '--ts-var-spotterviz-thinking-inprogress-header-color'?: string; + /** + * Color of the thinking step header when completed in SpotterViz. + */ + '--ts-var-spotterviz-thinking-completed-header-color'?: string; + /** + * Color of the final completion indicator icon shown when all work is done. + * The last green dot which is shown when work is done. + */ + '--ts-var-spotterviz-thinking-work-done-icon-color'?: string; + /** + * Hover background color for the working/work done CTA in SpotterViz. + */ + '--ts-var-spotterviz-thinking-cta-hover-background'?: string; + /** + * Background color of tool call panels in SpotterViz. + */ + '--ts-var-spotterviz-tool-call-background'?: string; + /** + * Color of tool titles and icons in SpotterViz. + */ + '--ts-var-spotterviz-tool-title-color'?: string; + /** + * Border color of tool call panels in SpotterViz. + */ + '--ts-var-spotterviz-tool-border-color'?: string; + /** + * Background color of the JSON input panel inside a tool call in SpotterViz. + */ + '--ts-var-spotterviz-tool-json-input-background'?: string; + /** + * Background color of the upvote/downvote feedback buttons in SpotterViz. + */ + '--ts-var-spotterviz-tool-feedback-button-background'?: string; + /** + * Hover background color of the upvote/downvote feedback buttons in SpotterViz. + */ + '--ts-var-spotterviz-tool-feedback-button-hover'?: string; + /** + * Text color of the JSON input panel inside a tool call in SpotterViz. + */ + '--ts-var-spotterviz-tool-json-input-color'?: string; + /** + * Background color of the last checkpoint section in SpotterViz. + */ + '--ts-var-spotterviz-last-checkpoint-background'?: string; + /** + * Border color of the last checkpoint section in SpotterViz. + */ + '--ts-var-spotterviz-last-checkpoint-border'?: string; + /** + * Border color of the Liveboard edit header toolbar. + */ + '--ts-var-liveboard-edit-toolbar-border'?: string; + /** + * Background color of the selected section in the Liveboard edit header toolbar. + */ + '--ts-var-liveboard-edit-toolbar-selected-background'?: string; + /** + * Text color of the selected section in the Liveboard edit header toolbar. + */ + '--ts-var-liveboard-edit-toolbar-selected-text-color'?: string; + /** + * Text color of unselected items in the Liveboard edit header toolbar. + */ + '--ts-var-liveboard-edit-toolbar-text'?: string; + /** + * Hover background color of unselected items in the Liveboard edit header toolbar. + */ + '--ts-var-liveboard-edit-toolbar-hover-background'?: string; + /** + * Hover text color of unselected items in the Liveboard edit header toolbar. + */ + '--ts-var-liveboard-edit-toolbar-hover-text-color'?: string; + /** + * Text color of the SpotterViz footer. + */ + '--ts-var-spotterviz-footer-text-color'?: string; + /** + * Shared border color used throughout SpotterViz: input box, user message, + * header underline, left panel border, thinking step connector and dots. + */ + '--ts-var-spotterviz-border-color'?: string; +} +//# sourceMappingURL=css-variables.d.ts.map \ No newline at end of file diff --git a/dist/src/css-variables.d.ts.map b/dist/src/css-variables.d.ts.map new file mode 100644 index 00000000..8098ec21 --- /dev/null +++ b/dist/src/css-variables.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"css-variables.d.ts","sourceRoot":"","sources":["css-variables.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IAC/B;;OAEG;IACH,0BAA0B,CAAC,EAAE,MAAM,CAAC;IAEpC;;OAEG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAE/B;;OAEG;IACH,2BAA2B,CAAC,EAAE,MAAM,CAAC;IAErC;;OAEG;IACH,8BAA8B,CAAC,EAAE,MAAM,CAAC;IAExC;;;;;;;;OAQG;IACH,4BAA4B,CAAC,EAAE,MAAM,CAAC;IAEtC;;OAEG;IACH,yBAAyB,CAAC,EAAE,MAAM,CAAC;IAEnC;;OAEG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B;;OAEG;IACH,wCAAwC,CAAC,EAAE,MAAM,CAAC;IAElD;;OAEG;IACH,wCAAwC,CAAC,EAAE,MAAM,CAAC;IAElD;;OAEG;IACH,yCAAyC,CAAC,EAAE,MAAM,CAAC;IAEnD;;OAEG;IACH,qCAAqC,CAAC,EAAE,MAAM,CAAC;IAE/C;;OAEG;IACH,sCAAsC,CAAC,EAAE,MAAM,CAAC;IAEhD;;OAEG;IACH,qCAAqC,CAAC,EAAE,MAAM,CAAC;IAE/C;;OAEG;IACH,gCAAgC,CAAC,EAAE,MAAM,CAAC;IAE1C;;OAEG;IACH,0CAA0C,CAAC,EAAE,MAAM,CAAC;IAEpD;;;OAGG;IACH,8CAA8C,CAAC,EAAE,MAAM,CAAC;IAExD;;;OAGG;IACH,qDAAqD,CAAC,EAAE,MAAM,CAAC;IAE/D;;OAEG;IACH,oDAAoD,CAAC,EAAE,MAAM,CAAC;IAE9D;;OAEG;IACH,0CAA0C,CAAC,EAAE,MAAM,CAAC;IAEpD;;;OAGG;IACH,kDAAkD,CAAC,EAAE,MAAM,CAAC;IAE5D;;OAEG;IACH,mCAAmC,CAAC,EAAE,MAAM,CAAC;IAE7C;;OAEG;IACH,oCAAoC,CAAC,EAAE,MAAM,CAAC;IAE9C;;OAEG;IACH,6CAA6C,CAAC,EAAE,MAAM,CAAC;IAEvD;;;OAGG;IACH,6CAA6C,CAAC,EAAE,MAAM,CAAC;IAEvD;;OAEG;IACH,sDAAsD,CAAC,EAAE,MAAM,CAAC;IAEhE;;OAEG;IACH,6DAA6D,CAAC,EAAE,MAAM,CAAC;IAEvE;;;OAGG;IACH,+BAA+B,CAAC,EAAE,MAAM,CAAC;IAEzC;;;OAGG;IACH,qCAAqC,CAAC,EAAE,MAAM,CAAC;IAE/C;;;OAGG;IACH,gCAAgC,CAAC,EAAE,MAAM,CAAC;IAE1C;;OAEG;IACH,uCAAuC,CAAC,EAAE,MAAM,CAAC;IAEjD;;;OAGG;IACH,qCAAqC,CAAC,EAAE,MAAM,CAAC;IAE/C;;OAEG;IACH,4CAA4C,CAAC,EAAE,MAAM,CAAC;IAEtD;;OAEG;IACH,6CAA6C,CAAC,EAAE,MAAM,CAAC;IAEvD;;OAEG;IACH,kCAAkC,CAAC,EAAE,MAAM,CAAC;IAE5C;;OAEG;IACH,yCAAyC,CAAC,EAAE,MAAM,CAAC;IAEnD;;OAEG;IACH,uCAAuC,CAAC,EAAE,MAAM,CAAC;IAEjD;;OAEG;IACH,8CAA8C,CAAC,EAAE,MAAM,CAAC;IAExD;;OAEG;IACH,+CAA+C,CAAC,EAAE,MAAM,CAAC;IAEzD;;;OAGG;IACH,iCAAiC,CAAC,EAAE,MAAM,CAAC;IAE3C;;OAEG;IACH,sCAAsC,CAAC,EAAE,MAAM,CAAC;IAEhD;;OAEG;IACH,6CAA6C,CAAC,EAAE,MAAM,CAAC;IAEvD;;OAEG;IACH,8CAA8C,CAAC,EAAE,MAAM,CAAC;IAExD;;OAEG;IACH,0BAA0B,CAAC,EAAE,MAAM,CAAC;IAEpC;;OAEG;IACH,gCAAgC,CAAC,EAAE,MAAM,CAAC;IAE1C;;OAEG;IACH,mCAAmC,CAAC,EAAE,MAAM,CAAC;IAE7C;;OAEG;IACH,gCAAgC,CAAC,EAAE,MAAM,CAAC;IAE1C;;;OAGG;IACH,sCAAsC,CAAC,EAAE,MAAM,CAAC;IAEhD;;;OAGG;IACH,yCAAyC,CAAC,EAAE,MAAM,CAAC;IAEnD;;OAEG;IACH,4BAA4B,CAAC,EAAE,MAAM,CAAC;IAEtC;;OAEG;IACH,yBAAyB,CAAC,EAAE,MAAM,CAAC;IAEnC;;OAEG;IACH,yBAAyB,CAAC,EAAE,MAAM,CAAC;IAEnC;;OAEG;IACH,sCAAsC,CAAC,EAAE,MAAM,CAAC;IAEhD;;OAEG;IACH,yCAAyC,CAAC,EAAE,MAAM,CAAC;IAEnD;;;OAGG;IACH,wCAAwC,CAAC,EAAE,MAAM,CAAC;IAElD;;OAEG;IACH,6BAA6B,CAAC,EAAE,MAAM,CAAC;IAEvC;;OAEG;IACH,0BAA0B,CAAC,EAAE,MAAM,CAAC;IAEpC;;OAEG;IACH,0BAA0B,CAAC,EAAE,MAAM,CAAC;IAEpC;;OAEG;IACH,6BAA6B,CAAC,EAAE,MAAM,CAAC;IAEvC;;OAEG;IACH,kCAAkC,CAAC,EAAE,MAAM,CAAC;IAE5C;;OAEG;IACH,4BAA4B,CAAC,EAAE,MAAM,CAAC;IAEtC;;OAEG;IACH,iCAAiC,CAAC,EAAE,MAAM,CAAC;IAE3C;;OAEG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAE/B;;OAEG;IACH,iCAAiC,CAAC,EAAE,MAAM,CAAC;IAE3C;;OAEG;IACH,2BAA2B,CAAC,EAAE,MAAM,CAAC;IAErC;;OAEG;IACH,iCAAiC,CAAC,EAAE,MAAM,CAAC;IAE3C;;OAEG;IACH,gCAAgC,CAAC,EAAE,MAAM,CAAC;IAE1C;;OAEG;IACH,sCAAsC,CAAC,EAAE,MAAM,CAAC;IAEhD;;OAEG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAE/B;;OAEG;IACH,0BAA0B,CAAC,EAAE,MAAM,CAAC;IAEpC;;OAEG;IACH,2BAA2B,CAAC,EAAE,MAAM,CAAC;IAErC;;OAEG;IACH,8BAA8B,CAAC,EAAE,MAAM,CAAC;IAExC;;OAEG;IACH,iCAAiC,CAAC,EAAE,MAAM,CAAC;IAE3C;;OAEG;IACH,mCAAmC,CAAC,EAAE,MAAM,CAAC;IAE7C;;OAEG;IACH,iCAAiC,CAAC,EAAE,MAAM,CAAC;IAE3C;;OAEG;IACH,4BAA4B,CAAC,EAAE,MAAM,CAAC;IAEtC;;OAEG;IACH,mCAAmC,CAAC,EAAE,MAAM,CAAC;IAE7C;;OAEG;IACH,8BAA8B,CAAC,EAAE,MAAM,CAAC;IAExC;;OAEG;IACH,mCAAmC,CAAC,EAAE,MAAM,CAAC;IAE7C;;OAEG;IACH,mCAAmC,CAAC,EAAE,MAAM,CAAC;IAE7C;;OAEG;IACH,gCAAgC,CAAC,EAAE,MAAM,CAAC;IAE1C;;OAEG;IACH,2CAA2C,CAAC,EAAE,MAAM,CAAC;IAErD;;OAEG;IACH,6CAA6C,CAAC,EAAE,MAAM,CAAC;IAEvD;;OAEG;IACH,mDAAmD,CAAC,EAAE,MAAM,CAAC;IAE7D;;OAEG;IACH,mDAAmD,CAAC,EAAE,MAAM,CAAC;IAE7D;;OAEG;IACH,mDAAmD,CAAC,EAAE,MAAM,CAAC;IAC7D;;OAEG;IACH,gCAAgC,CAAC,EAAE,MAAM,CAAC;IAC1C;;OAEG;IACH,gCAAgC,CAAC,EAAE,MAAM,CAAC;IAC1C;;OAEG;IACH,gCAAgC,CAAC,EAAE,MAAM,CAAC;IAC1C;;OAEG;IACH,gCAAgC,CAAC,EAAE,MAAM,CAAC;IAC1C;;OAEG;IACH,iCAAiC,CAAC,EAAE,MAAM,CAAC;IAC3C;;OAEG;IACH,oCAAoC,CAAC,EAAE,MAAM,CAAC;IAC9C;;OAEG;IACH,oCAAoC,CAAC,EAAE,MAAM,CAAC;IAE9C;;OAEG;IACH,sCAAsC,CAAC,EAAE,MAAM,CAAC;IAEhD;;OAEG;IACH,sCAAsC,CAAC,EAAE,MAAM,CAAC;IAEhD;;OAEG;IACH,sCAAsC,CAAC,EAAE,MAAM,CAAC;IAEhD;;OAEG;IACH,sCAAsC,CAAC,EAAE,MAAM,CAAC;IAEhD;;OAEG;IACH,oCAAoC,CAAC,EAAE,MAAM,CAAC;IAE9C;;OAEG;IACH,uCAAuC,CAAC,EAAE,MAAM,CAAC;IAEjD;;OAEG;IACH,iCAAiC,CAAC,EAAE,MAAM,CAAC;IAE3C;;OAEG;IACH,iDAAiD,CAAC,EAAE,MAAM,CAAC;IAE3D;;;;;;OAMG;IACH,kCAAkC,CAAC,EAAE,MAAM,CAAC;IAE5C;;;;;;OAMG;IACH,0CAA0C,CAAC,EAAE,MAAM,CAAC;IAEpD;;;;;;OAMG;IACH,4CAA4C,CAAC,EAAE,MAAM,CAAC;IAEtD;;;;;;OAMG;IACH,+CAA+C,CAAC,EAAE,MAAM,CAAC;IAEzD;;;;;;OAMG;IACH,iDAAiD,CAAC,EAAE,MAAM,CAAC;IAE3D;;;;;;OAMG;IACH,uCAAuC,CAAC,EAAE,MAAM,CAAC;IAEjD;;OAEG;IACH,uCAAuC,CAAC,EAAE,MAAM,CAAC;IAEjD;;;;;;OAMG;IACH,qCAAqC,CAAC,EAAE,MAAM,CAAC;IAE/C;;;;;;OAMG;IACH,uCAAuC,CAAC,EAAE,MAAM,CAAC;IAEjD;;OAEG;IACH,iDAAiD,CAAC,EAAE,MAAM,CAAC;IAE3D;;OAEG;IACH,8CAA8C,CAAC,EAAE,MAAM,CAAC;IAExD;;;;;;OAMG;IACH,2CAA2C,CAAC,EAAE,MAAM,CAAC;IAErD;;;;;;OAMG;IACH,iDAAiD,CAAC,EAAE,MAAM,CAAC;IAE3D;;;;;;OAMG;IACH,gDAAgD,CAAC,EAAE,MAAM,CAAC;IAE1D;;;;;;OAMG;IACH,sDAAsD,CAAC,EAAE,MAAM,CAAC;IAEhE;;;;;;OAMG;IACH,0CAA0C,CAAC,EAAE,MAAM,CAAC;IAEpD;;;;;;OAMG;IACH,oCAAoC,CAAC,EAAE,MAAM,CAAC;IAE9C;;;;;;OAMG;IACH,+BAA+B,CAAC,EAAE,MAAM,CAAC;IAEzC;;;;;;OAMG;IACH,2CAA2C,CAAC,EAAE,MAAM,CAAC;IAErD;;;;;;OAMG;IACH,4CAA4C,CAAC,EAAE,MAAM,CAAC;IAEtD;;OAEG;IACH,2BAA2B,CAAC,EAAE,MAAM,CAAC;IAErC;;OAEG;IACH,wCAAwC,CAAC,EAAE,MAAM,CAAC;IAElD;;OAEG;IACH,2CAA2C,CAAC,EAAE,MAAM,CAAC;IAErD;;OAEG;IACH,6CAA6C,CAAC,EAAE,MAAM,CAAC;IAEvD;;OAEG;IACH,mDAAmD,CAAC,EAAE,MAAM,CAAC;IAE7D;;OAEG;IACH,4CAA4C,CAAC,EAAE,MAAM,CAAC;IAEtD;;OAEG;IACH,oCAAoC,CAAC,EAAE,MAAM,CAAC;IAE9C;;OAEG;IACH,wCAAwC,CAAC,EAAE,MAAM,CAAC;IAElD;;OAEG;IACH,0CAA0C,CAAC,EAAE,MAAM,CAAC;IAEpD;;OAEG;IACH,oCAAoC,CAAC,EAAE,MAAM,CAAC;IAE9C;;OAEG;IACH,oCAAoC,CAAC,EAAE,MAAM,CAAC;IAE9C;;OAEG;IACH,0CAA0C,CAAC,EAAE,MAAM,CAAC;IAEpD;;OAEG;IACH,0CAA0C,CAAC,EAAE,MAAM,CAAC;IAEpD;;OAEG;IACH,2CAA2C,CAAC,EAAE,MAAM,CAAC;IAErD;;OAEG;IACH,2CAA2C,CAAC,EAAE,MAAM,CAAC;IAErD;;OAEG;IACH,oDAAoD,CAAC,EAAE,MAAM,CAAC;IAE9D;;OAEG;IACH,oDAAoD,CAAC,EAAE,MAAM,CAAC;IAE9D;;OAEG;IACH,qDAAqD,CAAC,EAAE,MAAM,CAAC;IAE/D;;OAEG;IACH,sDAAsD,CAAC,EAAE,MAAM,CAAC;IAEhE;;OAEG;IACH,4CAA4C,CAAC,EAAE,MAAM,CAAC;IAEtD;;OAEG;IACH,4CAA4C,CAAC,EAAE,MAAM,CAAC;IAEtD;;OAEG;IACH,qDAAqD,CAAC,EAAE,MAAM,CAAC;IAE/D;;OAEG;IACH,qDAAqD,CAAC,EAAE,MAAM,CAAC;IAE/D;;OAEG;IACH,6CAA6C,CAAC,EAAE,MAAM,CAAC;IAEvD;;OAEG;IACH,8CAA8C,CAAC,EAAE,MAAM,CAAC;IAExD;;OAEG;IACH,yBAAyB,CAAC,EAAE,MAAM,CAAC;IAEnC;;OAEG;IACH,+BAA+B,CAAC,EAAE,MAAM,CAAC;IAEzC;;OAEG;IACH,iCAAiC,CAAC,EAAE,MAAM,CAAC;IAE3C;;OAEG;IACH,2CAA2C,CAAC,EAAE,MAAM,CAAC;IAErD;;OAEG;IACH,2CAA2C,CAAC,EAAE,MAAM,CAAC;IAErD;;OAEG;IACH,2CAA2C,CAAC,EAAE,MAAM,CAAC;IAErD;;OAEG;IACH,2CAA2C,CAAC,EAAE,MAAM,CAAC;IAErD;;OAEG;IACH,oCAAoC,CAAC,EAAE,MAAM,CAAC;IAE9C;;OAEG;IACH,oCAAoC,CAAC,EAAE,MAAM,CAAC;IAE9C;;OAEG;IACH,8CAA8C,CAAC,EAAE,MAAM,CAAC;IAExD;;OAEG;IACH,qDAAqD,CAAC,EAAE,MAAM,CAAC;IAE/D;;OAEG;IACH,iDAAiD,CAAC,EAAE,MAAM,CAAC;IAE3D;;OAEG;IACH,+CAA+C,CAAC,EAAE,MAAM,CAAC;IAEzD;;OAEG;IACH,0DAA0D,CAAC,EAAE,MAAM,CAAC;IAEpE;;OAEG;IACH,8CAA8C,CAAC,EAAE,MAAM,CAAC;IAExD;;OAEG;IACH,6BAA6B,CAAC,EAAE,MAAM,CAAC;IAEvC;;OAEG;IACH,mCAAmC,CAAC,EAAE,MAAM,CAAC;IAE7C;;OAEG;IACH,yBAAyB,CAAC,EAAE,MAAM,CAAC;IAEnC;;OAEG;IACH,iCAAiC,CAAC,EAAE,MAAM,CAAC;IAE3C;;OAEG;IACH,oCAAoC,CAAC,EAAE,MAAM,CAAC;IAE9C;;OAEG;IACH,kCAAkC,CAAC,EAAE,MAAM,CAAC;IAE5C;;OAEG;IACH,6BAA6B,CAAC,EAAE,MAAM,CAAC;IAEvC;;OAEG;IACH,mCAAmC,CAAC,EAAE,MAAM,CAAC;IAE7C;;OAEG;IACH,sCAAsC,CAAC,EAAE,MAAM,CAAC;IAEhD;;OAEG;IACH,oCAAoC,CAAC,EAAE,MAAM,CAAC;IAE9C;;OAEG;IACH,qCAAqC,CAAC,EAAE,MAAM,CAAC;IAE/C;;OAEG;IACH,oCAAoC,CAAC,EAAE,MAAM,CAAC;IAE9C;;OAEG;IACH,0CAA0C,CAAC,EAAE,MAAM,CAAC;IAEpD;;OAEG;IACH,6CAA6C,CAAC,EAAE,MAAM,CAAC;IAEvD;;OAEG;IACH,+CAA+C,CAAC,EAAE,MAAM,CAAC;IAEzD;;OAEG;IACH,8CAA8C,CAAC,EAAE,MAAM,CAAC;IAExD;;OAEG;IACH,8CAA8C,CAAC,EAAE,MAAM,CAAC;IAExD;;OAEG;IACH,oDAAoD,CAAC,EAAE,MAAM,CAAC;IAE9D;;OAEG;IACH,qDAAqD,CAAC,EAAE,MAAM,CAAC;IAE/D;;OAEG;IACH,oDAAoD,CAAC,EAAE,MAAM,CAAC;IAE9D;;OAEG;IACH,0CAA0C,CAAC,EAAE,MAAM,CAAC;IAEpD;;OAEG;IACH,qDAAqD,CAAC,EAAE,MAAM,CAAC;IAE/D;;OAEG;IACH,sCAAsC,CAAC,EAAE,MAAM,CAAC;IAEhD;;OAEG;IACH,sCAAsC,CAAC,EAAE,MAAM,CAAC;IAEhD;;OAEG;IACH,6CAA6C,CAAC,EAAE,MAAM,CAAC;IAEvD;;OAEG;IACH,qCAAqC,CAAC,EAAE,MAAM,CAAC;IAE/C;;OAEG;IACH,2CAA2C,CAAC,EAAE,MAAM,CAAC;IAErD;;;OAGG;IACH,iDAAiD,CAAC,EAAE,MAAM,CAAC;IAE3D;;OAEG;IACH,4CAA4C,CAAC,EAAE,MAAM,CAAC;IAEtD;;OAEG;IACH,kDAAkD,CAAC,EAAE,MAAM,CAAC;IAE5D;;;OAGG;IACH,kCAAkC,CAAC,EAAE,MAAM,CAAC;IAE5C;;OAEG;IACH,oCAAoC,CAAC,EAAE,MAAM,CAAC;IAE9C;;OAEG;IACH,wCAAwC,CAAC,EAAE,MAAM,CAAC;IAElD;;OAEG;IACH,4CAA4C,CAAC,EAAE,MAAM,CAAC;IAEtD;;OAEG;IACH,iDAAiD,CAAC,EAAE,MAAM,CAAC;IAE3D;;OAEG;IACH,sDAAsD,CAAC,EAAE,MAAM,CAAC;IAEhE;;OAEG;IACH,qDAAqD,CAAC,EAAE,MAAM,CAAC;IAE/D;;;OAGG;IACH,mDAAmD,CAAC,EAAE,MAAM,CAAC;IAE7D;;OAEG;IACH,mDAAmD,CAAC,EAAE,MAAM,CAAC;IAE7D;;OAEG;IACH,0CAA0C,CAAC,EAAE,MAAM,CAAC;IAEpD;;OAEG;IACH,sCAAsC,CAAC,EAAE,MAAM,CAAC;IAEhD;;OAEG;IACH,uCAAuC,CAAC,EAAE,MAAM,CAAC;IAEjD;;OAEG;IACH,gDAAgD,CAAC,EAAE,MAAM,CAAC;IAE1D;;OAEG;IACH,qDAAqD,CAAC,EAAE,MAAM,CAAC;IAE/D;;OAEG;IACH,gDAAgD,CAAC,EAAE,MAAM,CAAC;IAE1D;;OAEG;IACH,2CAA2C,CAAC,EAAE,MAAM,CAAC;IAErD;;OAEG;IACH,gDAAgD,CAAC,EAAE,MAAM,CAAC;IAE1D;;OAEG;IACH,4CAA4C,CAAC,EAAE,MAAM,CAAC;IAEtD;;OAEG;IACH,wCAAwC,CAAC,EAAE,MAAM,CAAC;IAElD;;OAEG;IACH,qDAAqD,CAAC,EAAE,MAAM,CAAC;IAE/D;;OAEG;IACH,qDAAqD,CAAC,EAAE,MAAM,CAAC;IAE/D;;OAEG;IACH,sCAAsC,CAAC,EAAE,MAAM,CAAC;IAEhD;;OAEG;IACH,kDAAkD,CAAC,EAAE,MAAM,CAAC;IAE5D;;OAEG;IACH,kDAAkD,CAAC,EAAE,MAAM,CAAC;IAE5D;;OAEG;IACH,uCAAuC,CAAC,EAAE,MAAM,CAAC;IAEjD;;;OAGG;IACH,kCAAkC,CAAC,EAAE,MAAM,CAAC;CAC/C"} \ No newline at end of file diff --git a/dist/src/embed/app.d.ts b/dist/src/embed/app.d.ts new file mode 100644 index 00000000..baacf2db --- /dev/null +++ b/dist/src/embed/app.d.ts @@ -0,0 +1,921 @@ +/** + * Copyright (c) 2022 + * + * Full application embedding + * https://developers.thoughtspot.com/docs/?pageid=full-embed + * @summary Full app embed + * @module + * @author Ayon Ghosh + */ +import { DOMSelector, MessagePayload, AllEmbedViewConfig, DefaultAppInitData, VisualizationOverrides } from '../types'; +import { V1Embed } from './ts-embed'; +import { SpotterChatViewConfig, SpotterSidebarViewConfig } from './conversation'; +import { SpotterVizConfig } from './spotter-viz-utils'; +/** + * Pages within the ThoughtSpot app that can be embedded. + */ +export declare enum Page { + /** + * Home page + */ + Home = "home", + /** + * Search page + */ + Search = "search", + /** + * Saved answers listing page + */ + Answers = "answers", + /** + * Liveboards listing page + */ + Liveboards = "liveboards", + /** + * @hidden + */ + Pinboards = "pinboards", + /** + * Data management page + */ + Data = "data", + /** + * SpotIQ listing page + */ + SpotIQ = "insights", + /** + * Monitor Alerts Page + */ + Monitor = "monitor" +} +/** + * Define the initial state of column custom group accordions + * in data panel v2. + */ +export declare enum DataPanelCustomColumnGroupsAccordionState { + /** + * Expand all the accordion initially in data panel v2. + */ + EXPAND_ALL = "EXPAND_ALL", + /** + * Collapse all the accordions initially in data panel v2. + */ + COLLAPSE_ALL = "COLLAPSE_ALL", + /** + * Expand the first accordion and collapse the rest. + */ + EXPAND_FIRST = "EXPAND_FIRST" +} +export declare enum HomePageSearchBarMode { + OBJECT_SEARCH = "objectSearch", + AI_ANSWER = "aiAnswer", + NONE = "none" +} +/** + * Define the version of the primary navbar + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ +export declare enum PrimaryNavbarVersion { + /** + * Sliding (v3) introduces a new left-side navigation hub featuring a tab switcher, + * along with updates to the top navigation bar. + * It serves as the foundational version of the PrimaryNavBar. + */ + Sliding = "v3" +} +/** + * Define the version of the home page + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ +export declare enum HomePage { + /** + * Modular (v2) introduces the updated Modular Home Experience. + * It serves as the foundational version of the home page. + */ + Modular = "v2", + /** + * ModularWithStylingChanges (v3) introduces Modular Home Experience + * with styling changes. + */ + ModularWithStylingChanges = "v3", + /** + * Focused (v4) introduces the V4 homepage experience + * in which Watchlist and recents and incorporated together + * to form a more focused homepage. + * Pre-requisite : spotter enablement + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + */ + Focused = "v4" +} +/** + * Define the version of the list page + * @version SDK: 1.40.0 | ThoughtSpot: 10.12.0.cl + */ +export declare enum ListPage { + /** + * List (v2) is the traditional List Experience. + * It serves as the foundational version of the list page. + */ + List = "v2", + /** + * ListWithUXChanges (v3) introduces the new updated list page with UX changes. + */ + ListWithUXChanges = "v3" +} +/** + * Define the discovery experience + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ +export interface DiscoveryExperience { + /** + * primaryNavbarVersion determines the version of the primary navigation bar. + */ + primaryNavbarVersion?: PrimaryNavbarVersion; + /** + * homePage determines the version of the home page. + */ + homePage?: HomePage; + /** + * listPageVersion determines the version of the list page. + */ + listPageVersion?: ListPage; +} +/** + * The view configuration for full app embedding. + * @group Embed components + */ +export interface AppViewConfig extends AllEmbedViewConfig { + /** + * If true, the top navigation bar within the ThoughtSpot app + * is displayed. By default, the navigation bar is hidden. + * This flag also controls the homepage left navigation bar. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.2.0 | ThoughtSpot: 8.4.0.cl + * @default true + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... // other embed view config + * showPrimaryNavbar:true, + * }) + * ``` + */ + showPrimaryNavbar?: boolean; + /** + * Control the visibility of the left navigation panel on the home page + * in the V2 and V3 navigation and home page experience. + * If `showPrimaryNavbar` is true, that is, if the Global and Homepage + * navigation bars are visible, this flag will only hide the left navigation bar + * on the home page. + * The `showPrimaryNavbar` flag takes precedence over the `hideHomepageLeftNav`. + * + * **Note**: This attribute is not supported in the classic (V1) experience. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + * @default false + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... // other embed view config + * hideHomepageLeftNav : true, + * }) + * ``` + */ + hideHomepageLeftNav?: boolean; + /** + * Control the visibility of the help (?) and profile + * buttons on the top navigation bar. + * These buttons are visible if the + * navigation bar is not hidden via `showPrimaryNavbar`. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.2.0 | ThoughtSpot: 8.4.0.cl + * @default false + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... // other embed view config + * disableProfileAndHelp: true, + * }) + * ``` + */ + disableProfileAndHelp?: boolean; + /** + * Whether the help menu in the top navigation bar should be served + * from Pendo or ThoughtSpot's internal help items. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.36.3 | ThoughtSpot: 10.1.0.cl + * @default true + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... // other embed view config + * enablePendoHelp: false, + * }); + * ``` + */ + enablePendoHelp?: boolean; + /** + * Control the visibility of the hamburger icon on + * the top navigation bar in the V3 navigation experience. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + * @default false + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... // other embed view config + * hideHamburger : true, + * }) + * ``` + */ + hideHamburger?: boolean; + /** + * Control the visibility of the object search + * on the top navigation bar in the + * V2 and V3 navigation experience. + * + * **Note**: This attribute is not supported + * in the classic (V1) experience. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + * @default true + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... // other embed view config + * hideObjectSearch: false, + * }) + * ``` + */ + hideObjectSearch?: boolean; + /** + * Control the visibility of the notification icon + * on the top navigation bar in V3 navigation experience. + * + * **Note**: This attribute is not supported + * in the classic (V1) and V2 experience modes. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + * @default true + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... // other embed view config + * hideNotification: false, + * }) + * ``` + */ + hideNotification?: boolean; + /** + * Control the visibility of the application selection menu + * in the top navigation bar in the V2 experience. + * In the V3 experience, it shows or hides application selection + * icons on the left navigation panel. + * By default, the application selection menu and icons are + * shown in the UI. + * + * **Note**: This attribute is not supported in the classic (V1) experience. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + * @default false + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... // other embed view config + * hideApplicationSwitcher : true, + * }) + * ``` + */ + hideApplicationSwitcher?: boolean; + /** + * Control the visibility of the Org switcher button on the nav-bar. + * By default, the Org switcher button is shown. + * + * **Note**: This attribute is not supported in the classic (V1) experience. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + * @default true + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... // other embed view config + * hideOrgSwitcher : true, + * }) + * ``` + */ + hideOrgSwitcher?: boolean; + /** + * A URL path to the embedded application page + * If both path and pageId attributes are defined, the path definition + * takes precedence. This is the path post the `#/` in the URL of the standalone + * ThoughtSpot app. Use this to open the embedded view to a specific path. + * + * For example, if you want the component to open to a specific Liveboard + * you could set the path to `pinboard//tab/`. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.1.0 | ThoughtSpot: 9.4.0.cl + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... // other embed view config + * path:"pinboard/1234/tab/7464", + * }) + * ``` + */ + path?: string; + /** + * The application page to set as the start page + * in the embedded view. + * + * Use this to open to a particular page in the app. To open to a specific + * path within the app, use the `path` attribute which is more flexible. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.1.0 | ThoughtSpot: 9.4.0.cl + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... // other embed view config + * pageId: Page.Answers, // or Page.Data + * }) + * ``` + */ + pageId?: Page; + /** + * This puts a filter tag on the application. All metadata lists in the + * application, such as Liveboards and answers, would be filtered by this + * tag. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.1.0 | ThoughtSpot: 9.4.0.cl + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... // other embed view config + * tag:'value', + * }) + * ``` + */ + tag?: string; + /** + * Hide tag filter chips that appear when content is filtered by tags. + * When enabled, this automatically: + * - Hides tag filter indicators/chips from the UI + * + * This provides a clean interface without tag-related UI elements. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.44.0 | ThoughtSpot: 10.15.0.cl + * @example + * ```js + * // Simple usage - automatically hides all tag-related UI + * const embed = new AppEmbed('#tsEmbed', { + * ... // other embed view config + * tag: 'Some Tag', + * hideTagFilterChips: true, // This is all you need! + * }); + * ``` + */ + hideTagFilterChips?: boolean; + /** + * The array of GUIDs to be hidden + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1-sw + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... // other embed view config + * hideObjects: [ + * '430496d6-6903-4601-937e-2c691821af3c', + * 'f547ec54-2a37-4516-a222-2b06719af726' + * ] + * }) + * ``` + */ + hideObjects?: string[]; + /** + * Render liveboards using the new v2 rendering mode + * This is a transient flag which is primarily meant for internal use + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1-sw + * @hidden + */ + liveboardV2?: boolean; + /** + * If set to true, the Search Assist feature is enabled. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.13.0 | ThoughtSpot: 8.5.0.cl, 8.8.1-sw + * @default true + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... // other embed view config + * enableSearchAssist: true, + * }) + * ``` + */ + enableSearchAssist?: boolean; + /** + * If set to true, the Liveboard container dynamically resizes + * according to the height of the Liveboard. + * + * **Note**: Using fullHeight loads all visualizations + * on the Liveboard simultaneously, which results in + * multiple warehouse queries and potentially a + * longer wait for the topmost visualizations to + * display on the screen. Setting fullHeight to + * `false` fetches visualizations incrementally as + * users scroll the page to view the charts and tables. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.21.0 | ThoughtSpot: 9.4.0.cl, 9.4.0-sw + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... // other embed view config + * fullHeight: true, + * }) + * ``` + */ + fullHeight?: boolean; + /** + * Enables the V2 navigation and modular home page experience. + * For more information, + * see link:https://developers.thoughtspot.com/docs/full-app-customize[full app embed documentation]. + * Supported embed types: `AppEmbed` + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + * @default false + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... // other embed view config + * modularHomeExperience : true, + * }) + * ``` + */ + modularHomeExperience?: boolean; + /** + * Configures the V3 navigation and home page experience. + * For more information, see + * link:https://developers.thoughtspot.com/docs/full-app-customize[full app embed documentation]. + * Supported embed types: `AppEmbed` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + * @default false + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * // Enable V3 navigation and home page experience + * discoveryExperience : { + * primaryNavbarVersion: PrimaryNavbarVersion.Sliding, // Enable V3 navigation + * homePage: HomePage.ModularWithStylingChanges, // Enable V3 modular home page + * ... // other embed view config + * }, + * }) + * ``` + */ + discoveryExperience?: DiscoveryExperience; + /** + * To set the initial state of the search bar in case of saved-answers. Use {@link collapseSearchBar} instead. + * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl + * @deprecated This flag is deprecated. + * @default false + */ + collapseSearchBarInitially?: boolean; + /** + * This controls the initial behaviour of custom column groups accordion. + * It takes DataPanelCustomColumnGroupsAccordionState enum values as input. + * List of different enum values:- + * - EXPAND_ALL: Expand all the accordion initially in data panel v2. + * - COLLAPSE_ALL: Collapse all the accordions initially in data panel v2. + * - EXPAND_FIRST: Expand the first accordion and collapse the rest. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl + * @default DataPanelCustomColumnGroupsAccordionState.EXPAND_ALL + * @example + * ```js + * const embed = new AppEmbed('#embed', { + * ... // other app view config + * dataPanelCustomGroupsAccordionInitialState: + * DataPanelCustomColumnGroupsAccordionState.EXPAND_ALL, + * }); + * ``` + */ + dataPanelCustomGroupsAccordionInitialState?: DataPanelCustomColumnGroupsAccordionState; + /** + * Flag to use home page search bar mode + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.33.0 | ThoughtSpot: 10.3.0.cl + */ + homePageSearchBarMode?: HomePageSearchBarMode; + /** + * This flag is used to enable unified search experience for full app embed. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.34.0 | ThoughtSpot: 10.5.0.cl + * @default true + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... // other embed view config + * isUnifiedSearchExperienceEnabled: true, + * }) + * ``` + */ + isUnifiedSearchExperienceEnabled?: boolean; + /** + * This flag is used to enable the new connection experience for AppEmbed. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.51.0 | ThoughtSpot Cloud: 26.8.0.cl + * @default false + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... // other embed view config + * newConnectionsExperience: true, + * }) + * ``` + */ + newConnectionsExperience?: boolean; + /** + * This flag is used to enable/disable the styling and grouping in a Liveboard. Use {@link isLiveboardMasterpiecesEnabled} instead. + * @deprecated This flag is deprecated. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @type {boolean} + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * isLiveboardStylingAndGroupingEnabled: true, + * }) + * ``` + */ + isLiveboardStylingAndGroupingEnabled?: boolean; + /** + * This flag is used to enable/disable the png embedding of liveboard in scheduled + * mails + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @type {boolean} + * @version SDK: 1.42.0 | ThoughtSpot: 10.14.0.cl + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * isPNGInScheduledEmailsEnabled: true, + * }) + * ``` + */ + isPNGInScheduledEmailsEnabled?: boolean; + /** + * Enables the 'what you see is what you get' PDF export for Liveboards. Each tab is rendered on a single page + * following the exact UI layout, instead of splitting visualizations across multiple A4 pages. + * This feature is GA from version 26.5.0.cl. It is disabled by default in embed deployments. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @type {boolean} + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * isContinuousLiveboardPDFEnabled: true, + * }) + * ``` + */ + isContinuousLiveboardPDFEnabled?: boolean; + /** + * This flag is used to enable/disable the XLSX/CSV download option for Liveboards + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @type {boolean} + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * isLiveboardXLSXCSVDownloadEnabled: true, + * }) + * ``` + */ + isLiveboardXLSXCSVDownloadEnabled?: boolean; + /** + * This flag is used to enable/disable the granular XLSX/CSV schedules feature + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @type {boolean} + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * isGranularXLSXCSVSchedulesEnabled: true, + * }) + * ``` + */ + isGranularXLSXCSVSchedulesEnabled?: boolean; + /** + * This flag is used to enable the full height lazy load data. + * + * @type {boolean} + * @version SDK: 1.40.0 | ThoughtSpot: 10.12.0.cl + * @default false + * @example + * ```js + * const embed = new AppEmbed('#embed-container', { + * // ...other options + * fullHeight: true, + * lazyLoadingForFullHeight: true, + * }) + * ``` + */ + lazyLoadingForFullHeight?: boolean; + /** + * The margin to be used for lazy loading. + * + * For example, if the margin is set to '10px', + * the visualization will be loaded 10px before its top edge is visible in the + * viewport. + * + * The format is similar to CSS margin. + * + * @type {string} + * @version SDK: 1.40.0 | ThoughtSpot: 10.12.0.cl + * @example + * ```js + * const embed = new AppEmbed('#embed-container', { + * // ...other options + * fullHeight: true, + * lazyLoadingForFullHeight: true, + * // Using 0px, the visualization will be only loaded when it's visible in the viewport. + * lazyLoadingMargin: '0px', + * }) + * ``` + */ + lazyLoadingMargin?: string; + /** + * updatedSpotterChatPrompt : Controls the updated spotter chat prompt. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.45.0 | ThoughtSpot: 26.2.0.cl + * @default false + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... //other embed view config + * updatedSpotterChatPrompt : true, + * }) + * ``` + */ + updatedSpotterChatPrompt?: boolean; + /** + * Controls the visibility of the past conversations sidebar. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + * @deprecated from SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl + * Use `spotterSidebarConfig.enablePastConversationsSidebar`. + * @default false + */ + enablePastConversationsSidebar?: boolean; + /** + * Configuration for the Spotter sidebar UI customization. + * Only applicable when navigating to Spotter within the app. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * // Deprecated standalone flag (backward compatibility) + * enablePastConversationsSidebar: false, + * // Recommended config; this value takes precedence + * spotterSidebarConfig: { + * enablePastConversationsSidebar: true, + * spotterSidebarTitle: 'My Conversations', + * }, + * }) + * ``` + */ + spotterSidebarConfig?: SpotterSidebarViewConfig; + /** + * Configuration for customizing Spotter chat UI + * branding in tool response cards. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.46.0 | ThoughtSpot: 26.4.0.cl + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... //other embed view config + * spotterChatConfig: { + * hideToolResponseCardBranding: true, + * toolResponseCardBrandingLabel: 'MyBrand', + * }, + * }) + * ``` + */ + spotterChatConfig?: SpotterChatViewConfig; + /** + * Configuration for the SpotterViz interface shown on the Liveboard. + * Customize the brand name, description, chat input placeholder, + * starter prompts, and visibility of starter prompts in the SpotterViz panel. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * const embed = new AppEmbed('#embed-container', { + * ... // other options + * spotterViz: { + * brandName: 'MyBrand', + * brandHeadline: 'Hi, there! I\'m', + * description: 'Ask questions about your data', + * inputChatPlaceholder: 'Ask a question...', + * hideStarterPrompts: false, + * customStarterPrompts: [{ id: '1', displayText: 'Top products', fullPrompt: 'What are the top products by revenue?' }] + * }, + * }) + * ``` + */ + spotterViz?: SpotterVizConfig; + /** + * Enables the stop answer generation button in the Spotter embed UI, + * allowing users to interrupt an ongoing answer generation. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + * @default false + */ + enableStopAnswerGenerationEmbed?: boolean; + /** + * This is the minimum height (in pixels) for a full-height App. + * Setting this height helps resolve issues with empty Apps and + * other screens navigable from an App. + * + * @version SDK: 1.44.2 | ThoughtSpot: 10.15.0.cl + * @default 500 + * @example + * ```js + * const embed = new AppEmbed('#embed', { + * ... // other app view config + * fullHeight: true, + * minimumHeight: 600, + * }); + * ``` + */ + minimumHeight?: number; + /** + * To enable the homepage announcement banner. + * Controls the visibility of the announcement section + * on the homepage. + * + * Supported embed types: `AppEmbed` + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... // other embed view config + * enableHomepageAnnouncement: true, + * }) + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + enableHomepageAnnouncement?: boolean; + /** + * If set to true, enables visualization data caching on the Liveboard. + * @type {boolean} + * @version SDK: 1.49.0 | ThoughtSpot: 26.6.0.cl + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... // other options + * enableLiveboardDataCache: true, + * }) + * ``` + */ + enableLiveboardDataCache?: boolean; + /** + * Visual overrides to customize the chart or table properties. + * @version SDK: 1.49.0 | ThoughtSpot: 26.6.0.cl + */ + visualOverrides?: VisualizationOverrides; +} +/** + * APP_INIT data shape for AppEmbed. + * @internal + */ +export interface AppEmbedAppInitData extends DefaultAppInitData { + embedParams?: { + spotterSidebarConfig?: SpotterSidebarViewConfig; + spotterVizConfig?: SpotterVizConfig; + }; +} +/** + * Embeds full ThoughtSpot experience in a host application. + * @group Embed components + */ +export declare class AppEmbed extends V1Embed { + protected viewConfig: AppViewConfig; + private defaultHeight; + constructor(domSelector: DOMSelector, viewConfig: AppViewConfig); + /** + * Extends the default APP_INIT payload with `embedParams.spotterSidebarConfig` + * so the conv-assist app can read sidebar configuration on initialisation. + * + * Precedence for `enablePastConversationsSidebar`: + * `spotterSidebarConfig.enablePastConversationsSidebar` wins over the + * deprecated top-level `enablePastConversationsSidebar` flag; if the former + * is absent the latter is used as a fallback. + * + * An invalid `spotterDocumentationUrl` triggers a validation error and is + * excluded from the payload rather than forwarded to the app. + */ + protected getAppInitData(): Promise; + /** + * Constructs a map of parameters to be passed on to the + * embedded Liveboard or visualization. + */ + protected getEmbedParams(): string; + private sendFullHeightLazyLoadData; + /** + * This is a handler for the RequestVisibleEmbedCoordinates event. + * It is used to send the visible coordinates data to the host application. + * @param data The event payload + * @param responder The responder function + */ + private requestVisibleEmbedCoordinatesHandler; + /** + * Constructs the URL of the ThoughtSpot app page to be rendered. + * @param pageId The ID of the page to be embedded. + */ + getIFrameSrc(): string; + /** + * Set the iframe height as per the computed height received + * from the ThoughtSpot app. + * @param data The event payload + */ + protected updateIFrameHeight: (data: MessagePayload) => void; + private embedIframeCenter; + private setIframeHeightForNonEmbedLiveboard; + /** + * Gets the ThoughtSpot route of the page for a particular page ID. + * @param pageId The identifier for a page in the ThoughtSpot app. + * @param modularHomeExperience + */ + private getPageRoute; + /** + * Formats the path provided by the user. + * @param path The URL path. + * @returns The URL path that the embedded app understands. + */ + private formatPath; + /** + * Navigate to particular page for app embed. eg:answers/pinboards/home + * This is used for embedding answers, pinboards, visualizations and full application + * only. + * @param path string | number The string, set to iframe src and navigate to new page + * eg: appEmbed.navigateToPage('pinboards') + * When used with `noReload` (default: true) this can also be a number + * like 1/-1 to go forward/back. + * @param noReload boolean Trigger the navigation without reloading the page + * @version SDK: 1.12.0 | ThoughtSpot: 8.4.0.cl, 8.4.1-sw + */ + navigateToPage(path: string | number, noReload?: boolean): void; + /** + * Destroys the ThoughtSpot embed, and remove any nodes from the DOM. + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + destroy(): void; + private postRender; + private registerLazyLoadEvents; + private unregisterLazyLoadEvents; + /** + * Renders the embedded application pages in the ThoughtSpot app. + * @param renderOptions An object containing the page ID + * to be embedded. + */ + render(): Promise; +} +//# sourceMappingURL=app.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/app.d.ts.map b/dist/src/embed/app.d.ts.map new file mode 100644 index 00000000..d66b46d2 --- /dev/null +++ b/dist/src/embed/app.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["app.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,EAEH,WAAW,EAGX,cAAc,EACd,kBAAkB,EAClB,kBAAkB,EAClB,sBAAsB,EAEzB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAEjF,OAAO,EAAE,gBAAgB,EAA8B,MAAM,qBAAqB,CAAC;AAEnF;;GAEG;AAEH,oBAAY,IAAI;IACZ;;OAEG;IACH,IAAI,SAAS;IACb;;OAEG;IACH,MAAM,WAAW;IACjB;;OAEG;IACH,OAAO,YAAY;IACnB;;OAEG;IACH,UAAU,eAAe;IACzB;;OAEG;IACH,SAAS,cAAc;IACvB;;OAEG;IACH,IAAI,SAAS;IACb;;OAEG;IACH,MAAM,aAAa;IACnB;;OAEG;IACH,OAAO,YAAY;CACtB;AAED;;;GAGG;AACH,oBAAY,yCAAyC;IACjD;;OAEG;IACH,UAAU,eAAe;IACzB;;OAEG;IACH,YAAY,iBAAiB;IAC7B;;OAEG;IACH,YAAY,iBAAiB;CAChC;AAED,oBAAY,qBAAqB;IAC7B,aAAa,iBAAiB;IAC9B,SAAS,aAAa;IACtB,IAAI,SAAS;CAChB;AAED;;;GAGG;AACH,oBAAY,oBAAoB;IAC5B;;;;OAIG;IACH,OAAO,OAAO;CACjB;AAED;;;GAGG;AACH,oBAAY,QAAQ;IAChB;;;OAGG;IACH,OAAO,OAAO;IACd;;;OAGG;IACH,yBAAyB,OAAO;IAChC;;;;;;OAMG;IACH,OAAO,OAAO;CACjB;AAED;;;GAGG;AACH,oBAAY,QAAQ;IAChB;;;OAGG;IACH,IAAI,OAAO;IACX;;OAEG;IACH,iBAAiB,OAAO;CAC3B;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAChC;;OAEG;IACH,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C;;OAEG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB;;OAEG;IACH,eAAe,CAAC,EAAE,QAAQ,CAAC;CAC9B;AAED;;;GAGG;AACH,MAAM,WAAW,aAAc,SAAQ,kBAAkB;IACrD;;;;;;;;;;;;;;;OAeG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;;;;;;;;;;;;;OAgBG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;;;;;;;;;;;;;OAcG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;;;;;;;;;OAcG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;;;;;;;;;;;;OAkBG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;;;;;;;;;;;OAiBG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;;;;;;;;;;;;;;;OAgBG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;;;;;;;;;;;;;OAkBG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,EAAE,IAAI,CAAC;IACd;;;;;;;;;;;;;;OAcG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;;;;;;;;;;;;;OAkBG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;;;;;;;;;;;OAeG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;;;;;;;;;OAaG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;;;;;;;OAcG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;;;;;;;;;;;;;;;;;OAkBG;IACH,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C;;;;;OAKG;IACH,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,0CAA0C,CAAC,EAAE,yCAAyC,CAAC;IACvF;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IAC9C;;;;;;;;;;;;;OAaG;IACH,gCAAgC,CAAC,EAAE,OAAO,CAAC;IAE3C;;;;;;;;;;;;;OAaG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAEnC;;;;;;;;;;;;;;;OAeG;IACH,oCAAoC,CAAC,EAAE,OAAO,CAAC;IAE/C;;;;;;;;;;;;;;;OAeG;IACH,6BAA6B,CAAC,EAAE,OAAO,CAAC;IAExC;;;;;;;;;;;;;;;;OAgBG;IACH,+BAA+B,CAAC,EAAE,OAAO,CAAC;IAE1C;;;;;;;;;;;;;;OAcG;IACH,iCAAiC,CAAC,EAAE,OAAO,CAAC;IAE5C;;;;;;;;;;;;;;OAcG;IACH,iCAAiC,CAAC,EAAE,OAAO,CAAC;IAE5C;;;;;;;;;;;;;;OAcG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAEnC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;;;;;;;;;;;OAaG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC;;;;;;;;OAQG;IACH,8BAA8B,CAAC,EAAE,OAAO,CAAC;IACzC;;;;;;;;;;;;;;;;;;OAkBG;IACH,oBAAoB,CAAC,EAAE,wBAAwB,CAAC;IAChD;;;;;;;;;;;;;;;;OAgBG;IACH,iBAAiB,CAAC,EAAE,qBAAqB,CAAC;IAC1C;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B;;;;;;;OAOG;IACH,+BAA+B,CAAC,EAAE,OAAO,CAAC;IAC1C;;;;;;;;;;;;;;;OAeG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;;;;OAcG;IACH,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC;;;;;;;;;;;OAWG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAEnC;;;OAGG;IACH,eAAe,CAAC,EAAE,sBAAsB,CAAC;CAC5C;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAoB,SAAQ,kBAAkB;IAC3D,WAAW,CAAC,EAAE;QACV,oBAAoB,CAAC,EAAE,wBAAwB,CAAC;QAChD,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;KACvC,CAAC;CACL;AAED;;;GAGG;AACH,qBAAa,QAAS,SAAQ,OAAO;IACjC,SAAS,CAAC,UAAU,EAAE,aAAa,CAAC;IAEpC,OAAO,CAAC,aAAa,CAAO;gBAEhB,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa;IAc/D;;;;;;;;;;;OAWG;cACa,cAAc,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAU9D;;;OAGG;IACH,SAAS,CAAC,cAAc;IA6RxB,OAAO,CAAC,0BAA0B,CAMjC;IAED;;;;;OAKG;IACH,OAAO,CAAC,qCAAqC,CAI5C;IAED;;;OAGG;IACI,YAAY,IAAI,MAAM;IAW7B;;;;OAIG;IACH,SAAS,CAAC,kBAAkB,SAAU,cAAc,UAGlD;IAEF,OAAO,CAAC,iBAAiB,CAGvB;IAEF,OAAO,CAAC,mCAAmC,CAqBzC;IAEF;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAsBpB;;;;OAIG;IACH,OAAO,CAAC,UAAU;IAalB;;;;;;;;;;OAUG;IACI,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,UAAQ,GAAG,IAAI;IAsBpE;;;OAGG;IACI,OAAO;IAKd,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,sBAAsB;IAQ9B,OAAO,CAAC,wBAAwB;IAOhC;;;;OAIG;IACU,MAAM,IAAI,OAAO,CAAC,QAAQ,CAAC;CAS3C"} \ No newline at end of file diff --git a/dist/src/embed/app.spec.d.ts b/dist/src/embed/app.spec.d.ts new file mode 100644 index 00000000..da009ad3 --- /dev/null +++ b/dist/src/embed/app.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=app.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/app.spec.d.ts.map b/dist/src/embed/app.spec.d.ts.map new file mode 100644 index 00000000..87f127c2 --- /dev/null +++ b/dist/src/embed/app.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"app.spec.d.ts","sourceRoot":"","sources":["app.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/embed/auto-frame-renderer.d.ts b/dist/src/embed/auto-frame-renderer.d.ts new file mode 100644 index 00000000..75b66b30 --- /dev/null +++ b/dist/src/embed/auto-frame-renderer.d.ts @@ -0,0 +1,41 @@ +import { AutoMCPFrameRendererViewConfig } from "../types"; +/** + * Starts an automatic renderer that watches the DOM for iframes containing + * the `tsmcp=true` query parameter and replaces them with fully configured + * ThoughtSpot embed iframes. The query parameter is automatically added by + * the ThoughtSpot MCP server. + * + * A {@link MutationObserver} is set up on `document.body` to detect both + * directly added iframes and iframes nested within added container elements. + * Each matching iframe is replaced in-place with a new ThoughtSpot embed + * iframe that merges the original iframe's query parameters with the SDK + * embed parameters. + * + * Call {@link MutationObserver.disconnect | observer.disconnect()} on the + * returned observer to stop monitoring the DOM. + * + * @param viewConfig - Optional configuration for the auto-rendered embeds. + * Accepts all properties from {@link AutoMCPFrameRendererViewConfig}. + * Defaults to an empty config. + * @returns A {@link MutationObserver} instance that is actively observing + * `document.body`. Disconnect it when monitoring is no longer needed. + * + * @example + * ```js + * import { startAutoMCPFrameRenderer } from '@thoughtspot/visual-embed-sdk'; + * + * // Start watching the DOM for tsmcp iframes + * const observer = startAutoMCPFrameRenderer({ + * // optional view config overrides + * }); + * + * // Later, stop watching + * observer.disconnect(); + * ``` + * + * @example + * Detailed example of how to use the auto-frame renderer: + * [Python React Agent Simple UI](https://github.com/thoughtspot/developer-examples/tree/main/mcp/python-react-agent-simple-ui) + */ +export declare function startAutoMCPFrameRenderer(viewConfig?: AutoMCPFrameRendererViewConfig): MutationObserver; +//# sourceMappingURL=auto-frame-renderer.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/auto-frame-renderer.d.ts.map b/dist/src/embed/auto-frame-renderer.d.ts.map new file mode 100644 index 00000000..3b6a8b28 --- /dev/null +++ b/dist/src/embed/auto-frame-renderer.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"auto-frame-renderer.d.ts","sourceRoot":"","sources":["auto-frame-renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,8BAA8B,EAAS,MAAM,UAAU,CAAC;AAKjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,GAAE,8BAAmC,oBA2BxF"} \ No newline at end of file diff --git a/dist/src/embed/auto-frame-renderer.spec.d.ts b/dist/src/embed/auto-frame-renderer.spec.d.ts new file mode 100644 index 00000000..ca06a5b4 --- /dev/null +++ b/dist/src/embed/auto-frame-renderer.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=auto-frame-renderer.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/auto-frame-renderer.spec.d.ts.map b/dist/src/embed/auto-frame-renderer.spec.d.ts.map new file mode 100644 index 00000000..37890fed --- /dev/null +++ b/dist/src/embed/auto-frame-renderer.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"auto-frame-renderer.spec.d.ts","sourceRoot":"","sources":["auto-frame-renderer.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/embed/base.d.ts b/dist/src/embed/base.d.ts new file mode 100644 index 00000000..faae3d21 --- /dev/null +++ b/dist/src/embed/base.d.ts @@ -0,0 +1,151 @@ +import { EmbedConfig, PrefetchFeatures } from '../types'; +import { notifyAuthFailure, notifyAuthSDKSuccess, notifyAuthSuccess, notifyLogout, AuthEventEmitter } from '../auth'; +import '../utils/with-resolvers-polyfill'; +export interface executeTMLInput { + metadata_tmls: string[]; + import_policy?: 'PARTIAL' | 'ALL_OR_NONE' | 'VALIDATE_ONLY'; + create_new?: boolean; +} +export interface exportTMLInput { + metadata: { + identifier: string; + type?: 'LIVEBOARD' | 'ANSWER' | 'LOGICAL_TABLE' | 'CONNECTION'; + }[]; + export_associated?: boolean; + export_fqn?: boolean; + edoc_format?: 'YAML' | 'JSON'; +} +export declare let authPromise: Promise; +export declare const getAuthPromise: () => Promise; +export { notifyAuthFailure, notifyAuthSDKSuccess, notifyAuthSuccess, notifyLogout, }; +/** + * Perform authentication on the ThoughtSpot app as applicable. + */ +export declare const handleAuth: () => Promise; +/** + * Prefetches static resources from the specified URL. Web browsers can then cache the + * prefetched resources and serve them from the user's local disk to provide faster access + * to your app. + * @param url The URL provided for prefetch + * @param prefetchFeatures Specify features which needs to be prefetched. + * @param additionalFlags This can be used to add any URL flag. + * @version SDK: 1.4.0 | ThoughtSpot: ts7.sep.cl, 7.2.1 + * @group Global methods + */ +export declare const prefetch: (url?: string, prefetchFeatures?: PrefetchFeatures[], additionalFlags?: { + [key: string]: string | number | boolean; +}) => void; +export declare const createAndSetInitPromise: () => void; +export declare const getInitPromise: () => Promise>; +export declare const getIsInitCompleted: () => boolean; +export declare const getIsInitCalled: () => boolean; +/** + * Initializes the Visual Embed SDK globally and perform + * authentication if applicable. This function needs to be called before any ThoughtSpot + * component like Liveboard etc can be embedded. But need not wait for AuthEvent.SUCCESS + * to actually embed. That is handled internally. + * @param embedConfig The configuration object containing ThoughtSpot host, + * authentication mechanism and so on. + * @example + * ```js + * const authStatus = init({ + * thoughtSpotHost: 'https://my.thoughtspot.cloud', + * authType: AuthType.None, + * }); + * authStatus.on(AuthStatus.FAILURE, (reason) => { // do something here }); + * ``` + * @returns {@link AuthEventEmitter} event emitter which emits events on authentication success, + * failure and logout. See {@link AuthStatus} + * @version SDK: 1.0.0 | ThoughtSpot ts7.april.cl, 7.2.1 + * @group Authentication / Init + */ +export declare const init: (embedConfig: EmbedConfig) => AuthEventEmitter | null; +/** + * + */ +export declare function disableAutoLogin(): void; +/** + * Logs out from ThoughtSpot. This also sets the autoLogin flag to false, to + * prevent the SDK from automatically logging in again. + * + * You can call the `init` method again to re login, if autoLogin is set to + * true in this second call it will be honored. + * @param doNotDisableAutoLogin This flag when passed will not disable autoLogin + * @returns Promise which resolves when logout completes. + * @version SDK: 1.10.1 | ThoughtSpot: 8.2.0.cl, 8.4.1-sw + * @group Global methods + */ +export declare const logout: (doNotDisableAutoLogin?: boolean) => Promise; +/** + * Renders functions in a queue, resolves to next function only after the callback next + * is called + * @param fn The function being registered + */ +export declare const renderInQueue: (fn: (next?: (val?: any) => void) => Promise) => Promise; +/** + * Imports TML representation of the metadata objects into ThoughtSpot. + * @param data + * @returns imports TML data into ThoughtSpot + * @example + * ```js + * executeTML({ + * //Array of metadata Tmls in string format + * metadata_tmls: [ + * "'\''{\"guid\":\"9bd202f5-d431-44bf-9a07-b4f7be372125\", + * \"liveboard\":{\"name\":\"Parameters Liveboard\"}}'\''" + * ], + * import_policy: 'PARTIAL', // Specifies the import policy for the TML import. + * create_new: false, // If selected, creates TML objects with new GUIDs. + * }).then(result => { + * console.log(result); + * }).catch(error => { + * console.error(error); + * }); + * ``` + * @version SDK: 1.23.0 | ThoughtSpot: 9.4.0.cl + * @group Global methods + */ +export declare const executeTML: (data: executeTMLInput) => Promise; +/** + * Exports TML representation of the metadata objects from ThoughtSpot in JSON or YAML + * format. + * @param data + * @returns exports TML data + * @example + * ```js + * exportTML({ + * metadata: [ + * { + * type: "LIVEBOARD", //Metadata Type + * identifier: "9bd202f5-d431-44bf-9a07-b4f7be372125" //Metadata Id + * } + * ], + * export_associated: false,//indicates whether to export associated metadata objects + * export_fqn: false, //Adds FQNs of the referenced objects.For example, if you are + * //exporting a Liveboard and its associated objects, the API + * //returns the Liveboard TML data with the FQNs of the referenced + * //worksheet. If the exported TML data includes FQNs, you don't need + * //to manually add FQNs of the referenced objects during TML import. + * edoc_format: "JSON" //It takes JSON or YAML value + * }).then(result => { + * console.log(result); + * }).catch(error => { + * console.error(error); + * }); + * ``` + * @version SDK: 1.23.0 | ThoughtSpot: 9.4.0.cl + * @group Global methods + */ +export declare const exportTML: (data: exportTMLInput) => Promise; +/** + * + */ +export declare function reset(): void; +/** + * Reloads the ThoughtSpot iframe. + * @version SDK: 1.43.1 + * @param iFrame + * @group Global methods + */ +export declare const reloadIframe: (iFrame: HTMLIFrameElement) => void; +//# sourceMappingURL=base.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/base.d.ts.map b/dist/src/embed/base.d.ts.map new file mode 100644 index 00000000..b7c65f9c --- /dev/null +++ b/dist/src/embed/base.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["base.ts"],"names":[],"mappings":"AAaA,OAAO,EACO,WAAW,EAAmB,gBAAgB,EAC3D,MAAM,UAAU,CAAC;AAClB,OAAO,EAMH,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EACjB,YAAY,EAEZ,gBAAgB,EAEnB,MAAM,SAAS,CAAC;AACjB,OAAO,kCAAkC,CAAC;AAgB1C,MAAM,WAAW,eAAe;IAC5B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,CAAC,EAAE,SAAS,GAAG,aAAa,GAAG,eAAe,CAAC;IAC5D,UAAU,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC3B,QAAQ,EAAE;QACN,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,CAAC,EAAE,WAAW,GAAG,QAAQ,GAAG,eAAe,GAAG,YAAY,CAAC;KAClE,EAAE,CAAC;IACJ,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACjC;AAED,eAAO,IAAI,WAAW,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;AAEzC,eAAO,MAAM,cAAc,QAAO,QAAQ,OAAO,CAAgB,CAAC;AAElE,OAAO,EACH,iBAAiB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,YAAY,GAC3E,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,UAAU,QAAO,QAAQ,OAAO,CAiB5C,CAAC;AASF;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ,SACX,MAAM,qBACO,gBAAgB,EAAE;;MAEtC,IAyCF,CAAC;AAqCF,eAAO,MAAM,uBAAuB,QAAO,IAuB1C,CAAC;AAIF,eAAO,MAAM,cAAc,QACvB,QACI,WAAW,WAAW,CAAC,CACqC,CAAC;AAErE,eAAO,MAAM,kBAAkB,QAAO,OAC6B,CAAC;AAEpE,eAAO,MAAM,eAAe,QAAO,OAA0D,CAAC;AAE9F;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,IAAI,gBAAiB,WAAW,KAAG,gBAAgB,GAAG,IAuClE,CAAC;AAEF;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,MAAM,uCAAoC,QAAQ,OAAO,CAQrE,CAAC;AAIF;;;;GAIG;AACH,eAAO,MAAM,aAAa,sBAAuB,GAAG,KAAK,IAAI,KAAK,QAAQ,GAAG,CAAC,KAAG,QAAQ,GAAG,CAQ3F,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,UAAU,SAAgB,eAAe,KAAG,QAAQ,GAAG,CAmCnE,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,SAAS,SAAgB,cAAc,KAAG,QAAQ,GAAG,CAoCjE,CAAC;AAGF;;GAEG;AACH,wBAAgB,KAAK,IAAI,IAAI,CAI5B;AAED;;;;;GAKG;AACH,eAAO,MAAM,YAAY,WAAY,iBAAiB,SAMrD,CAAC"} \ No newline at end of file diff --git a/dist/src/embed/base.spec.d.ts b/dist/src/embed/base.spec.d.ts new file mode 100644 index 00000000..339a7343 --- /dev/null +++ b/dist/src/embed/base.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=base.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/base.spec.d.ts.map b/dist/src/embed/base.spec.d.ts.map new file mode 100644 index 00000000..ec9c9e3c --- /dev/null +++ b/dist/src/embed/base.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"base.spec.d.ts","sourceRoot":"","sources":["base.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/embed/bodyless-conversation.d.ts b/dist/src/embed/bodyless-conversation.d.ts new file mode 100644 index 00000000..bed358d8 --- /dev/null +++ b/dist/src/embed/bodyless-conversation.d.ts @@ -0,0 +1,111 @@ +import { BaseViewConfig } from '../types'; +import { TsEmbed } from './ts-embed'; +/** + * Configuration for bodyless conversation options. + * @group Embed components + */ +export interface SpotterAgentEmbedViewConfig extends Omit { + /** + * The ID of the Model to use for the conversation. + */ + worksheetId: string; +} +/** + * Configuration for conversation options. + * Use {@link SpotterAgentEmbedViewConfig} instead + * @deprecated from SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + * @group Embed components + */ +export interface BodylessConversationViewConfig extends SpotterAgentEmbedViewConfig { +} +export interface SpotterAgentMessageViewConfig extends SpotterAgentEmbedViewConfig { + sessionId: string; + genNo: number; + acSessionId: string; + acGenNo: number; + convId: string; + messageId: string; +} +export declare class ConversationMessage extends TsEmbed { + protected viewConfig: SpotterAgentMessageViewConfig; + constructor(container: HTMLElement, viewConfig: SpotterAgentMessageViewConfig); + protected getEmbedParamsObject(): Record; + getIframeSrc(): string; + render(): Promise; +} +/** + * Create a conversation embed, which can be integrated inside + * chatbots or other conversational interfaces. + * @version SDK: 1.37.0 | ThoughtSpot: 10.9.0.cl + * @group Embed components + * @example + * ```js + * import { SpotterAgentEmbed } from '@thoughtspot/visual-embed-sdk'; + * + * const conversation = new SpotterAgentEmbed({ + * worksheetId: 'worksheetId', + * }); + * + * const { container, error } = await conversation.sendMessage('show me sales by region'); + * + * // append the container to the DOM + * document.body.appendChild(container); // or to any other element + * ``` + */ +export declare class SpotterAgentEmbed { + private viewConfig; + private conversationService; + constructor(viewConfig: SpotterAgentEmbedViewConfig); + sendMessage(userMessage: string): Promise<{ + error: any; + container?: undefined; + viz?: undefined; + } | { + container: HTMLDivElement; + viz: ConversationMessage; + error?: undefined; + }>; + /** + * Send a message to the conversation service and return only the data. + * @param userMessage - The message to send to the conversation service. + * @returns The data from the conversation service. + */ + sendMessageData(userMessage: string): Promise<{ + error: any; + data?: undefined; + } | { + data: { + convId: any; + messageId: any; + sessionId: any; + genNo: any; + acSessionId: any; + acGenNo: any; + }; + error?: undefined; + }>; +} +/** + * Create a conversation embed, which can be integrated inside + * chatbots or other conversational interfaces. + * Use {@link SpotterAgentEmbed} instead + * @deprecated from SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + * @group Embed components + * @example + * ```js + * import { SpotterAgentEmbed } from '@thoughtspot/visual-embed-sdk'; + * + * const conversation = new SpotterAgentEmbed({ + * worksheetId: 'worksheetId', + * }); + * + * const { container, error } = await conversation.sendMessage('show me sales by region'); + * + * // append the container to the DOM + * document.body.appendChild(container); // or to any other element + * ``` + */ +export declare class BodylessConversation extends SpotterAgentEmbed { + constructor(viewConfig: BodylessConversationViewConfig); +} +//# sourceMappingURL=bodyless-conversation.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/bodyless-conversation.d.ts.map b/dist/src/embed/bodyless-conversation.d.ts.map new file mode 100644 index 00000000..6690f360 --- /dev/null +++ b/dist/src/embed/bodyless-conversation.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"bodyless-conversation.d.ts","sourceRoot":"","sources":["bodyless-conversation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,cAAc,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAKrC;;;GAGG;AACH,MAAM,WAAW,2BAA4B,SAAQ,IAAI,CAAC,cAAc,EAAE,eAAe,CAAC;IACtF;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;CACvB;AAED;;;;;GAKG;AAEH,MAAM,WAAW,8BAA+B,SAAQ,2BAA2B;CAAG;AAEtF,MAAM,WAAW,6BAA8B,SAAQ,2BAA2B;IAC9E,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,mBAAoB,SAAQ,OAAO;IACR,SAAS,CAAC,UAAU,EAAE,6BAA6B;gBAA3E,SAAS,EAAE,WAAW,EAAY,UAAU,EAAE,6BAA6B;IAKvF,SAAS,CAAC,oBAAoB;IAOvB,YAAY;IA6BN,MAAM,IAAI,OAAO,CAAC,mBAAmB,CAAC;CAOtD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,iBAAiB;IAGd,OAAO,CAAC,UAAU;IAF9B,OAAO,CAAC,mBAAmB,CAAsB;gBAE7B,UAAU,EAAE,2BAA2B;IAS9C,WAAW,CAAC,WAAW,EAAE,MAAM;;;;;;;;;IAqB5C;;;;OAIG;IACU,eAAe,CAAC,WAAW,EAAE,MAAM;;;;;;;;;;;;;;CAkBnD;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,oBAAqB,SAAQ,iBAAiB;gBAC3C,UAAU,EAAE,8BAA8B;CAGzD"} \ No newline at end of file diff --git a/dist/src/embed/bodyless-conversation.spec.d.ts b/dist/src/embed/bodyless-conversation.spec.d.ts new file mode 100644 index 00000000..c25d9035 --- /dev/null +++ b/dist/src/embed/bodyless-conversation.spec.d.ts @@ -0,0 +1,2 @@ +import 'jest-fetch-mock'; +//# sourceMappingURL=bodyless-conversation.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/bodyless-conversation.spec.d.ts.map b/dist/src/embed/bodyless-conversation.spec.d.ts.map new file mode 100644 index 00000000..4e2b275f --- /dev/null +++ b/dist/src/embed/bodyless-conversation.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"bodyless-conversation.spec.d.ts","sourceRoot":"","sources":["bodyless-conversation.spec.ts"],"names":[],"mappings":"AAAA,OAAO,iBAAiB,CAAC"} \ No newline at end of file diff --git a/dist/src/embed/conversation.d.ts b/dist/src/embed/conversation.d.ts new file mode 100644 index 00000000..37c28c36 --- /dev/null +++ b/dist/src/embed/conversation.d.ts @@ -0,0 +1,427 @@ +import { BaseViewConfig, RuntimeFilter, RuntimeParameter, DefaultAppInitData, VisualizationOverrides, SpotterFileUploadFileTypes } from '../types'; +import { TsEmbed } from './ts-embed'; +/** + * Configuration for search options + */ +export interface SearchOptions { + /** + * The query string to pass to start the Conversation. + */ + searchQuery: string; +} +/** + * Configuration for the Spotter sidebar. + * Can be used in SpotterEmbed and AppEmbed. + * @group Embed components + * @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl + */ +export interface SpotterSidebarViewConfig { + /** + * Controls the visibility of the past conversations sidebar. + * @default false + * @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl + */ + enablePastConversationsSidebar?: boolean; + /** + * Custom title text for the sidebar header. + * Defaults to translated "Spotter" text. + * @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl + */ + spotterSidebarTitle?: string; + /** + * Boolean to set the default expanded state of the sidebar. + * @default false + * @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl + */ + spotterSidebarDefaultExpanded?: boolean; + /** + * Custom label text for the rename action in the conversation edit menu. + * Defaults to translated "Rename" text. + * @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl + */ + spotterChatRenameLabel?: string; + /** + * Custom label text for the delete action in the conversation edit menu. + * Defaults to translated "DELETE" text. + * @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl + */ + spotterChatDeleteLabel?: string; + /** + * Custom title text for the delete conversation confirmation modal. + * Defaults to translated "Delete chat" text. + * @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl + */ + spotterDeleteConversationModalTitle?: string; + /** + * Custom message text for the past conversation banner alert. + * Defaults to translated alert message. + * @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl + */ + spotterPastConversationAlertMessage?: string; + /** + * Custom URL for the documentation/best practices link. + * Defaults to ThoughtSpot docs URL based on release version. + * Note: URL must include the protocol (e.g., `https://www.example.com`). + * @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl + */ + spotterDocumentationUrl?: string; + /** + * Custom label text for the best practices button in the footer. + * Defaults to translated "Best Practices" text. + * @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl + */ + spotterBestPracticesLabel?: string; + /** + * Number of conversations to fetch per batch when loading conversation history. + * @default 30 + * @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl + */ + spotterConversationsBatchSize?: number; + /** + * Custom title text for the "New Chat" button in the sidebar. + * Defaults to translated "New Chat" text. + * @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl + */ + spotterNewChatButtonTitle?: string; +} +/** + * Configuration for customizing Spotter chat UI branding. + * @version SDK: 1.46.0 | ThoughtSpot: 26.4.0.cl + * @group Embed components + */ +export interface SpotterChatViewConfig { + /** + * Hides the ThoughtSpot logo/icon in tool response + * cards. The branding label prefix is controlled + * separately via `toolResponseCardBrandingLabel`. + * External MCP tool branding is not affected. + * @default false + */ + hideToolResponseCardBranding?: boolean; + /** + * Custom label to replace the "ThoughtSpot" prefix + * in tool response cards. Set to an empty string + * `''` to hide the prefix entirely. Works + * independently of `hideToolResponseCardBranding`. + * External MCP tool branding is not affected. + */ + toolResponseCardBrandingLabel?: string; + /** + * Enables file upload in the Spotter chat interface. + * + * Supported embed types: `SpotterEmbed`, `LiveboardEmbed`, `AppEmbed` + * @version SDK: 1.49.0 | ThoughtSpot: 26.6.0.cl + * @default false + */ + spotterFileUploadEnabled?: boolean; + /** + * Restricts the allowed file types for Spotter file upload. + * + * Supported embed types: `SpotterEmbed`, `LiveboardEmbed`, `AppEmbed` + * @version SDK: 1.49.0 | ThoughtSpot: 26.6.0.cl + */ + spotterFileUploadFileTypes?: SpotterFileUploadFileTypes; +} +/** + * The configuration for the embedded spotterEmbed options. + * @group Embed components + */ +export interface SpotterEmbedViewConfig extends Omit { + /** + * The ID of the data source object. For example, Model, View, or Table. Spotter uses + * this object to query data and generate Answers. + */ + worksheetId: string; + /** + * Ability to pass a starting search query to the conversation. + */ + searchOptions?: SearchOptions; + /** + * disableSourceSelection : Disables data source selection + * but still display the selected data source. + * + * Supported embed types: `SpotterEmbed` + * @version SDK: 1.36.0 | ThoughtSpot: 10.6.0.cl + * @example + * ```js + * const embed = new SpotterEmbed('#tsEmbed', { + * ... //other embed view config + * disableSourceSelection : true, + * }) + * ``` + */ + disableSourceSelection?: boolean; + /** + * hideSourceSelection : Hide data source selection + * + * Supported embed types: `SpotterEmbed` + * @version SDK: 1.36.0 | ThoughtSpot: 10.6.0.cl + * @example + * ```js + * const embed = new SpotterEmbed('#tsEmbed', { + * ... //other embed view config + * hideSourceSelection : true, + * }) + * ``` + */ + hideSourceSelection?: boolean; + /** + * Flag to control Data panel experience + * + * Supported embed types: `SageEmbed`, `AppEmbed`, `SearchBarEmbed`, `LiveboardEmbed`, `SearchEmbed` + * @deprecated from SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + * @default true + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed, or SearchBarEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * dataPanelV2: true, + * }) + * ``` + */ + dataPanelV2?: boolean; + /** + * showSpotterLimitations : show limitation text + * of the spotter underneath the chat input. + * default is false. + * + * Supported embed types: `SpotterEmbed` + * @version SDK: 1.36.0 | ThoughtSpot: 10.5.0.cl + * @example + * ```js + * const embed = new SpotterEmbed('#tsEmbed', { + * ... //other embed view config + * showSpotterLimitations : true, + * }) + * ``` + */ + showSpotterLimitations?: boolean; + /** + * hideSampleQuestions : Hide sample questions on + * the initial screen of the conversation. + * + * Supported embed types: `SpotterEmbed` + * @version SDK: 1.36.0 | ThoughtSpot: 10.6.0.cl + * @example + * ```js + * const embed = new SpotterEmbed('#tsEmbed', { + * ... //other embed view config + * hideSampleQuestions : true, + * }) + * ``` + */ + hideSampleQuestions?: boolean; + /** + * The list of runtime filters to apply to a search Answer, + * visualization, or Liveboard. + * + * Supported embed types: `SpotterEmbed` + * @version SDK: 1.41.0 | ThoughtSpot: 10.13.0.cl + * @example + * ```js + * const embed = new SpotterEmbed('#tsEmbed', { + * // other embed view config + * runtimeFilters: [ + * { + * columnName: 'color', + * operator: RuntimeFilterOp.EQ, + * values: ['red'], + * }, + * ], + * }) + * ``` + */ + runtimeFilters?: RuntimeFilter[]; + /** + * Flag to control whether runtime filters should be included in the URL. + * If true, filters will be passed via app initialization payload + * (default behavior from SDK 1.45.0). + * If false/undefined, filters are appended to the iframe URL instead. + * (default behavior before SDK 1.45.0). + * + * Supported embed types: `SpotterEmbed` + * @version SDK: 1.41.0 | ThoughtSpot: 10.13.0.cl + * @default true + */ + excludeRuntimeFiltersfromURL?: boolean; + /** + * The list of runtime parameters to apply to the conversation. + * + * Supported embed types: `SpotterEmbed` + * @version SDK: 1.41.0 | ThoughtSpot: 10.13.0.cl + * @example + * ```js + * const embed = new SpotterEmbed('#tsEmbed', { + * // other embed view config + * runtimeParameters: [ + * { + * name: 'Integer Param', + * value: 10, + * }, + * ], + * }) + * ``` + */ + runtimeParameters?: RuntimeParameter[]; + /** + * Flag to control whether runtime parameters should be included in the URL. + * If true, parameters will be passed via app + * initialization payload (default behavior from SDK 1.45.0). + * If false/undefined, parameters are appended to + * the iframe URL instead (default behavior before SDK 1.45.0). + * + * Supported embed types: `SpotterEmbed` + * @version SDK: 1.41.0 | ThoughtSpot: 10.13.0.cl + * @default true + */ + excludeRuntimeParametersfromURL?: boolean; + /** + * updatedSpotterChatPrompt : Controls the updated spotter chat prompt. + * + * Supported embed types: `SpotterEmbed` + * @version SDK: 1.45.0 | ThoughtSpot: 26.2.0.cl + * @default false + * @example + * ```js + * const embed = new SpotterEmbed('#tsEmbed', { + * ... //other embed view config + * updatedSpotterChatPrompt : true, + * }) + * ``` + */ + updatedSpotterChatPrompt?: boolean; + /** + * Enables the stop answer generation button in the Spotter embed UI, + * allowing users to interrupt an ongoing answer generation. + * + * Supported embed types: `SpotterEmbed` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + * @default false + */ + enableStopAnswerGenerationEmbed?: boolean; + /** + * Controls the visibility of the past conversations sidebar. + * + * Supported embed types: `SpotterEmbed` + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + * @deprecated from SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl + * Use `spotterSidebarConfig.enablePastConversationsSidebar`. + * @default false + */ + enablePastConversationsSidebar?: boolean; + /** + * Configuration for the Spotter sidebar UI customization. + * + * Supported embed types: `SpotterEmbed`, `AppEmbed` + * @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl + * @example + * ```js + * const embed = new SpotterEmbed('#tsEmbed', { + * worksheetId: 'worksheet-id', + * // Deprecated standalone flag (backward compatibility) + * enablePastConversationsSidebar: false, + * // Recommended config; this value takes precedence + * spotterSidebarConfig: { + * enablePastConversationsSidebar: true, + * spotterSidebarTitle: 'My Conversations', + * spotterSidebarDefaultExpanded: true, + * }, + * }) + * ``` + */ + spotterSidebarConfig?: SpotterSidebarViewConfig; + /** + * Configuration for customizing Spotter chat UI + * branding in tool response cards. + * + * Supported embed types: `SpotterEmbed` + * @version SDK: 1.46.0 | ThoughtSpot: 26.4.0.cl + * @example + * ```js + * const embed = new SpotterEmbed('#tsEmbed', { + * ... //other embed view config + * spotterChatConfig: { + * hideToolResponseCardBranding: true, + * toolResponseCardBrandingLabel: 'MyBrand', + * }, + * }) + * ``` + */ + spotterChatConfig?: SpotterChatViewConfig; +} +/** + * The configuration for the embedded spotterEmbed options. + * Use {@link SpotterEmbedViewConfig} instead + * @deprecated from SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + * @group Embed components + */ +export interface ConversationViewConfig extends SpotterEmbedViewConfig { +} +/** + * APP_INIT data shape for SpotterEmbed. + * @internal + */ +export interface SpotterAppInitData extends DefaultAppInitData { + embedParams?: { + spotterSidebarConfig?: SpotterSidebarViewConfig; + visualOverridesParams?: VisualizationOverrides | null; + }; +} +/** + * Embed ThoughtSpot AI Conversation. + * @version SDK: 1.37.0 | ThoughtSpot: 10.9.0.cl + * @group Embed components + * @example + * ```js + * const conversation = new SpotterEmbed('#tsEmbed', { + * worksheetId: 'worksheetId', + * searchOptions: { + * searchQuery: 'searchQuery', + * }, + * }); + * conversation.render(); + * ``` + */ +export declare class SpotterEmbed extends TsEmbed { + protected viewConfig: SpotterEmbedViewConfig; + constructor(container: HTMLElement, viewConfig: SpotterEmbedViewConfig); + /** + * Extends the default APP_INIT payload with `embedParams.spotterSidebarConfig` + * so the conv-assist app can read sidebar configuration on initialisation. + * + * Precedence for `enablePastConversationsSidebar`: + * `spotterSidebarConfig.enablePastConversationsSidebar` wins over the + * deprecated top-level `enablePastConversationsSidebar` flag; if the former + * is absent the latter is used as a fallback. + * + * An invalid `spotterDocumentationUrl` triggers a validation error and is + * excluded from the payload rather than forwarded to the app. + */ + protected getAppInitData(): Promise; + protected getEmbedParamsObject(): Record; + getIframeSrc(): string; + render(): Promise; +} +/** + * Embed ThoughtSpot AI Conversation. + * Use {@link SpotterEmbed} instead + * @version SDK: 1.37.0 | ThoughtSpot: 10.9.0.cl + * @deprecated from SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + * @group Embed components + * @example + * ```js + * const conversation = new SpotterEmbed('#tsEmbed', { + * worksheetId: 'worksheetId', + * searchOptions: { + * searchQuery: 'searchQuery', + * }, + * }); + * conversation.render(); + * ``` + */ +export declare class ConversationEmbed extends SpotterEmbed { + protected viewConfig: ConversationViewConfig; + constructor(container: HTMLElement, viewConfig: ConversationViewConfig); +} +//# sourceMappingURL=conversation.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/conversation.d.ts.map b/dist/src/embed/conversation.d.ts.map new file mode 100644 index 00000000..3dcf5bab --- /dev/null +++ b/dist/src/embed/conversation.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["conversation.ts"],"names":[],"mappings":"AAEA,OAAO,EAAS,cAAc,EAAE,aAAa,EAAE,gBAAgB,EAAsC,kBAAkB,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,UAAU,CAAC;AAC9L,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAIrC;;GAEG;AACH,MAAM,WAAW,aAAa;IAC1B;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;CACvB;AAED;;;;;GAKG;AACH,MAAM,WAAW,wBAAwB;IACrC;;;;OAIG;IACH,8BAA8B,CAAC,EAAE,OAAO,CAAC;IACzC;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,6BAA6B,CAAC,EAAE,OAAO,CAAC;IACxC;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;;;;OAIG;IACH,mCAAmC,CAAC,EAAE,MAAM,CAAC;IAC7C;;;;OAIG;IACH,mCAAmC,CAAC,EAAE,MAAM,CAAC;IAC7C;;;;;OAKG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC;;;;OAIG;IACH,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC;;;;OAIG;IACH,6BAA6B,CAAC,EAAE,MAAM,CAAC;IACvC;;;;OAIG;IACH,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACtC;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IAClC;;;;;;OAMG;IACH,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC;;;;;;OAMG;IACH,6BAA6B,CAAC,EAAE,MAAM,CAAC;IACvC;;;;;;OAMG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC;;;;;OAKG;IACH,0BAA0B,CAAC,EAAE,0BAA0B,CAAC;CAC3D;AAED;;;GAGG;AACH,MAAM,WAAW,sBAAuB,SAAQ,IAAI,CAAC,cAAc,EAAE,eAAe,CAAC;IACjF;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;;;;;;;;;;;;;OAaG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;;OAYG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;;;;;;;;;;;OAcG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;;;;;;;;;;OAcG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;;;OAaG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;IACjC;;;;;;;;;;OAUG;IACH,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC;;;;;;;;;;;;;;;;;OAiBG;IACH,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACvC;;;;;;;;;;OAUG;IACH,+BAA+B,CAAC,EAAE,OAAO,CAAC;IAC1C;;;;;;;;;;;;;OAaG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC;;;;;;;OAOG;IACH,+BAA+B,CAAC,EAAE,OAAO,CAAC;IAC1C;;;;;;;;OAQG;IACH,8BAA8B,CAAC,EAAE,OAAO,CAAC;IACzC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,oBAAoB,CAAC,EAAE,wBAAwB,CAAC;IAChD;;;;;;;;;;;;;;;;OAgBG;IACH,iBAAiB,CAAC,EAAE,qBAAqB,CAAC;CAC7C;AAED;;;;;GAKG;AAEH,MAAM,WAAW,sBAAuB,SAAQ,sBAAsB;CAAG;AAEzE;;;GAGG;AACH,MAAM,WAAW,kBAAmB,SAAQ,kBAAkB;IAC1D,WAAW,CAAC,EAAE;QACV,oBAAoB,CAAC,EAAE,wBAAwB,CAAC;QAChD,qBAAqB,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAAC;KACzD,CAAC;CACL;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,YAAa,SAAQ,OAAO;IACD,SAAS,CAAC,UAAU,EAAE,sBAAsB;gBAApE,SAAS,EAAE,WAAW,EAAY,UAAU,EAAE,sBAAsB;IAUhF;;;;;;;;;;;OAWG;cACa,cAAc,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAK7D,SAAS,CAAC,oBAAoB;IA0DvB,YAAY,IAAI,MAAM;IAmChB,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC;CAO/C;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,iBAAkB,SAAQ,YAAY;IACX,SAAS,CAAC,UAAU,EAAE,sBAAsB;gBAApE,SAAS,EAAE,WAAW,EAAY,UAAU,EAAE,sBAAsB;CASnF"} \ No newline at end of file diff --git a/dist/src/embed/conversation.spec.d.ts b/dist/src/embed/conversation.spec.d.ts new file mode 100644 index 00000000..add5866a --- /dev/null +++ b/dist/src/embed/conversation.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=conversation.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/conversation.spec.d.ts.map b/dist/src/embed/conversation.spec.d.ts.map new file mode 100644 index 00000000..51d434d8 --- /dev/null +++ b/dist/src/embed/conversation.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"conversation.spec.d.ts","sourceRoot":"","sources":["conversation.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/embed/embed.spec.d.ts b/dist/src/embed/embed.spec.d.ts new file mode 100644 index 00000000..eccda3ae --- /dev/null +++ b/dist/src/embed/embed.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=embed.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/embed.spec.d.ts.map b/dist/src/embed/embed.spec.d.ts.map new file mode 100644 index 00000000..51ff32b4 --- /dev/null +++ b/dist/src/embed/embed.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"embed.spec.d.ts","sourceRoot":"","sources":["embed.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/embed/embedConfig.d.ts b/dist/src/embed/embedConfig.d.ts new file mode 100644 index 00000000..98706ed7 --- /dev/null +++ b/dist/src/embed/embedConfig.d.ts @@ -0,0 +1,62 @@ +import { EmbedConfig } from '../types'; +/** + * Gets the embed configuration settings that were used to + * initialize the SDK. + * @returns {@link EmbedConfig} + * + * @example + * ```js + * import { getInitConfig } from '@thoughtspot/visual-embed-sdk'; + * // Call the getInitConfig method to retrieve the embed configuration + * const config = getInitConfig(); + * // Log the configuration settings + * console.log(config); + * ``` + * Returns the link:https://developers.thoughtspot.com/docs/Interface_EmbedConfig[EmbedConfig] + * object, which contains the configuration settings used to + * initialize the SDK, including the following: + * + * - `thoughtSpotHost` - ThoughtSpot host URL + * - `authType`: The authentication method used. For example, + * `AuthServerCookieless` for `AuthType.TrustedAuthTokenCookieless` + * - `customizations` - Style, text, and icon customization settings + * that were applied during the SDK initialization. + * + * The following JSON output shows the embed configuration + * settings returned from the code in the previous example: + * + * @example + * ```json + * { + * "thoughtSpotHost": "https://{ThoughtSpot-Host}", + * "authType": "AuthServerCookieless", + * "customizations": { + * "style": { + * "customCSS": { + * "variables": { + * "--ts-var-button--secondary-background": "#7492d5", + * "--ts-var-button--secondary--hovers-background": "#aac2f8", + * "--ts-var-root-background": "#f1f4f8" + * } + * } + * } + * }, + * "loginFailedMessage": "Login failed, please try again", + * "authTriggerText": "Authorize", + * "disableTokenVerification": true, + * "authTriggerContainer": "#your-own-div" + * } + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw, and later + * @group Global methods + */ +export declare const getEmbedConfig: () => EmbedConfig; +/** + * Sets the configuration embed was initialized with. + * And returns the new configuration. + * @param newConfig The configuration to set. + * @version SDK: 1.27.0 | ThoughtSpot: 9.8.0.cl, 9.8.1.sw, and later + * @group Global methods + */ +export declare const setEmbedConfig: (newConfig: EmbedConfig) => any; +//# sourceMappingURL=embedConfig.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/embedConfig.d.ts.map b/dist/src/embed/embedConfig.d.ts.map new file mode 100644 index 00000000..779149ee --- /dev/null +++ b/dist/src/embed/embedConfig.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"embedConfig.d.ts","sourceRoot":"","sources":["embedConfig.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAIvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,eAAO,MAAM,cAAc,QAAO,WAA2D,CAAC;AAE9F;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,cAAe,WAAW,QAGpD,CAAC"} \ No newline at end of file diff --git a/dist/src/embed/events.spec.d.ts b/dist/src/embed/events.spec.d.ts new file mode 100644 index 00000000..c4dd5396 --- /dev/null +++ b/dist/src/embed/events.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=events.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/events.spec.d.ts.map b/dist/src/embed/events.spec.d.ts.map new file mode 100644 index 00000000..6fd61f43 --- /dev/null +++ b/dist/src/embed/events.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"events.spec.d.ts","sourceRoot":"","sources":["events.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/embed/hostEventClient/contracts.d.ts b/dist/src/embed/hostEventClient/contracts.d.ts new file mode 100644 index 00000000..409db3e3 --- /dev/null +++ b/dist/src/embed/hostEventClient/contracts.d.ts @@ -0,0 +1,194 @@ +import { ContextType, HostEvent, RuntimeFilter } from '../../types'; +import { SessionInterface } from '../../utils/graphql/answerService/answerService'; +export interface LiveboardTab { + id: string; + name: string; + [key: string]: any; +} +export declare enum UIPassthroughEvent { + PinAnswerToLiveboard = "addVizToPinboard", + SaveAnswer = "saveAnswer", + GetDiscoverabilityStatus = "getDiscoverabilityStatus", + GetAvailableUIPassthroughs = "getAvailableUiPassthroughs", + GetAnswerConfig = "getAnswerPageConfig", + GetLiveboardConfig = "getPinboardPageConfig", + GetUnsavedAnswerTML = "getUnsavedAnswerTML", + UpdateFilters = "updateFilters", + Drilldown = "drillDown", + GetAnswerSession = "getAnswerSession", + GetFilters = "getFilters", + GetIframeUrl = "getIframeUrl", + GetParameters = "getParameters", + GetTML = "getTML", + GetTabs = "getTabs", + GetExportRequestForCurrentPinboard = "getExportRequestForCurrentPinboard" +} +export type UIPassthroughContractBase = { + [UIPassthroughEvent.PinAnswerToLiveboard]: { + request: { + vizId?: string; + newVizName: string; + newVizDescription?: string; + liveboardId?: string; + tabId?: string; + newLiveboardName?: string; + newTabName?: string; + }; + response: { + liveboardId: string; + tabId: string; + vizId: string; + }; + }; + [UIPassthroughEvent.SaveAnswer]: { + request: { + name: string; + description: string; + vizId?: string; + isDiscoverable?: boolean; + }; + response: { + answerId: string; + saveResponse?: any; + shareResponse?: any; + }; + }; + [UIPassthroughEvent.GetDiscoverabilityStatus]: { + request: any; + response: { + shouldShowDiscoverability: boolean; + isDiscoverabilityCheckboxUnselectedPerOrg: boolean; + }; + }; + [UIPassthroughEvent.GetAvailableUIPassthroughs]: { + request: any; + response: { + keys: string[]; + }; + }; + [UIPassthroughEvent.GetAnswerConfig]: { + request: { + vizId?: string; + }; + response: any; + }; + [UIPassthroughEvent.GetLiveboardConfig]: { + request: any; + response: any; + }; + [UIPassthroughEvent.GetUnsavedAnswerTML]: { + request: { + sessionId?: string; + vizId?: string; + }; + response: { + tml: string; + }; + }; + [UIPassthroughEvent.GetAnswerSession]: { + request: { + vizId?: string; + }; + response: { + session: SessionInterface; + embedAnswerData?: Record; + }; + }; + [UIPassthroughEvent.GetFilters]: { + request: { + vizId?: string; + }; + response: { + liveboardFilters: Record[]; + runtimeFilters: RuntimeFilter[]; + }; + }; + [UIPassthroughEvent.GetIframeUrl]: { + request: Record; + response: { + iframeUrl: string; + }; + }; + [UIPassthroughEvent.GetParameters]: { + request: Record; + response: { + parameters: Record[]; + }; + }; + [UIPassthroughEvent.GetTML]: { + request: { + vizId?: string; + includeNonExecutedSearchTokens?: boolean; + }; + response: Record; + }; + [UIPassthroughEvent.GetTabs]: { + request: Record; + response: { + orderedTabIds: string[]; + numberOfTabs: number; + Tabs: LiveboardTab[]; + }; + }; + [UIPassthroughEvent.GetExportRequestForCurrentPinboard]: { + request: Record; + response: { + data: { + v2Content: string; + }; + type: UIPassthroughEvent.GetExportRequestForCurrentPinboard; + }; + }; + [UIPassthroughEvent.UpdateFilters]: { + request: { + filter?: { + column: string; + oper: string; + values: string[]; + type?: string; + }; + filters?: { + column: string; + oper: string; + values: string[]; + type?: string; + }[]; + }; + response: unknown; + }; + [UIPassthroughEvent.Drilldown]: { + request: { + points: { + selectedPoints?: string[]; + clickedPoint?: string; + }; + columnGuid?: string; + autoDrillDown?: boolean; + vizId?: string; + }; + response: unknown; + }; +}; +export type UIPassthroughRequest = UIPassthroughContractBase[T]['request']; +export type UIPassthroughResponse = UIPassthroughContractBase[T]['response']; +export type UIPassthroughArrayResponse = Array<{ + refId?: string; + value?: UIPassthroughResponse; + error?: any; +}>; +export type EmbedApiHostEventMapping = { + [HostEvent.Pin]: UIPassthroughEvent.PinAnswerToLiveboard; + [HostEvent.SaveAnswer]: UIPassthroughEvent.SaveAnswer; + [HostEvent.GetAnswerSession]: UIPassthroughEvent.GetAnswerSession; + [HostEvent.GetFilters]: UIPassthroughEvent.GetFilters; + [HostEvent.GetIframeUrl]: UIPassthroughEvent.GetIframeUrl; + [HostEvent.GetParameters]: UIPassthroughEvent.GetParameters; + [HostEvent.GetTML]: UIPassthroughEvent.GetTML; + [HostEvent.GetTabs]: UIPassthroughEvent.GetTabs; + [HostEvent.getExportRequestForCurrentPinboard]: UIPassthroughEvent.GetExportRequestForCurrentPinboard; +}; +export type HostEventRequest = HostEventT extends keyof EmbedApiHostEventMapping ? UIPassthroughRequest : any; +export type HostEventResponse = HostEventT extends keyof EmbedApiHostEventMapping ? UIPassthroughResponse : any; +export type TriggerPayload = PayloadT | HostEventRequest; +export type TriggerResponse = PayloadT extends HostEventRequest ? HostEventResponse : any; +//# sourceMappingURL=contracts.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/hostEventClient/contracts.d.ts.map b/dist/src/embed/hostEventClient/contracts.d.ts.map new file mode 100644 index 00000000..1b0f7e56 --- /dev/null +++ b/dist/src/embed/hostEventClient/contracts.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["contracts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iDAAiD,CAAC;AAEnF,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,oBAAY,kBAAkB;IAC5B,oBAAoB,qBAAqB;IACzC,UAAU,eAAe;IACzB,wBAAwB,6BAA6B;IACrD,0BAA0B,+BAA+B;IACzD,eAAe,wBAAwB;IACvC,kBAAkB,0BAA0B;IAC5C,mBAAmB,wBAAwB;IAC3C,aAAa,kBAAkB;IAC/B,SAAS,cAAc;IACvB,gBAAgB,qBAAqB;IACrC,UAAU,eAAe;IACzB,YAAY,iBAAiB;IAC7B,aAAa,kBAAkB;IAC/B,MAAM,WAAW;IACjB,OAAO,YAAY;IACnB,kCAAkC,uCAAuC;CAC1E;AAGD,MAAM,MAAM,yBAAyB,GAAG;IACtC,CAAC,kBAAkB,CAAC,oBAAoB,CAAC,EAAE;QACzC,OAAO,EAAE;YACP,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,UAAU,EAAE,MAAM,CAAC;YACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;YAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;YACrB,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,gBAAgB,CAAC,EAAE,MAAM,CAAC;YAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;SACrB,CAAC;QACF,QAAQ,EAAE;YACR,WAAW,EAAE,MAAM,CAAC;YACpB,KAAK,EAAE,MAAM,CAAC;YACd,KAAK,EAAE,MAAM,CAAC;SACf,CAAC;KACH,CAAC;IACF,CAAC,kBAAkB,CAAC,UAAU,CAAC,EAAE;QAC/B,OAAO,EAAE;YACP,IAAI,EAAE,MAAM,CAAC;YACb,WAAW,EAAE,MAAM,CAAC;YACpB,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,cAAc,CAAC,EAAE,OAAO,CAAC;SAC1B,CAAC;QACF,QAAQ,EAAE;YACR,QAAQ,EAAE,MAAM,CAAC;YACjB,YAAY,CAAC,EAAE,GAAG,CAAC;YACnB,aAAa,CAAC,EAAE,GAAG,CAAC;SACrB,CAAC;KACH,CAAC;IACF,CAAC,kBAAkB,CAAC,wBAAwB,CAAC,EAAE;QAC7C,OAAO,EAAE,GAAG,CAAC;QACb,QAAQ,EAAE;YACR,yBAAyB,EAAE,OAAO,CAAC;YACnC,yCAAyC,EAAE,OAAO,CAAC;SACpD,CAAC;KACH,CAAC;IACF,CAAC,kBAAkB,CAAC,0BAA0B,CAAC,EAAE;QAC/C,OAAO,EAAE,GAAG,CAAC;QACb,QAAQ,EAAE;YACR,IAAI,EAAE,MAAM,EAAE,CAAC;SAChB,CAAC;KACH,CAAC;IACF,CAAC,kBAAkB,CAAC,eAAe,CAAC,EAAE;QACpC,OAAO,EAAE;YACP,KAAK,CAAC,EAAE,MAAM,CAAC;SAChB,CAAC;QACF,QAAQ,EAAE,GAAG,CAAC;KACf,CAAC;IACF,CAAC,kBAAkB,CAAC,kBAAkB,CAAC,EAAE;QACvC,OAAO,EAAE,GAAG,CAAC;QACb,QAAQ,EAAE,GAAG,CAAC;KACf,CAAC;IACF,CAAC,kBAAkB,CAAC,mBAAmB,CAAC,EAAE;QACxC,OAAO,EAAE;YACP,SAAS,CAAC,EAAE,MAAM,CAAC;YACnB,KAAK,CAAC,EAAE,MAAM,CAAC;SAChB,CAAC;QACF,QAAQ,EAAE;YACR,GAAG,EAAE,MAAM,CAAC;SACb,CAAC;KACH,CAAC;IACF,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,EAAE;QACrC,OAAO,EAAE;YACP,KAAK,CAAC,EAAE,MAAM,CAAC;SAChB,CAAC;QACF,QAAQ,EAAE;YACR,OAAO,EAAE,gBAAgB,CAAC;YAC1B,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;SACvC,CAAC;KACH,CAAC;IACF,CAAC,kBAAkB,CAAC,UAAU,CAAC,EAAE;QAC/B,OAAO,EAAE;YACP,KAAK,CAAC,EAAE,MAAM,CAAC;SAChB,CAAC;QACF,QAAQ,EAAE;YACR,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;YACxC,cAAc,EAAE,aAAa,EAAE,CAAC;SACjC,CAAC;KACH,CAAC;IACF,CAAC,kBAAkB,CAAC,YAAY,CAAC,EAAE;QACjC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAC/B,QAAQ,EAAE;YACR,SAAS,EAAE,MAAM,CAAC;SACnB,CAAC;KACH,CAAC;IACF,CAAC,kBAAkB,CAAC,aAAa,CAAC,EAAE;QAClC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAC/B,QAAQ,EAAE;YACR,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;SACnC,CAAC;KACH,CAAC;IACF,CAAC,kBAAkB,CAAC,MAAM,CAAC,EAAE;QAC3B,OAAO,EAAE;YACP,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,8BAA8B,CAAC,EAAE,OAAO,CAAC;SAC1C,CAAC;QACF,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;KAC/B,CAAC;IACF,CAAC,kBAAkB,CAAC,OAAO,CAAC,EAAE;QAC5B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAC/B,QAAQ,EAAE;YACR,aAAa,EAAE,MAAM,EAAE,CAAC;YACxB,YAAY,EAAE,MAAM,CAAC;YACrB,IAAI,EAAE,YAAY,EAAE,CAAC;SACtB,CAAC;KACH,CAAC;IACF,CAAC,kBAAkB,CAAC,kCAAkC,CAAC,EAAE;QACvD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAC/B,QAAQ,EAAE;YACR,IAAI,EAAE;gBAAE,SAAS,EAAE,MAAM,CAAA;aAAE,CAAC;YAC5B,IAAI,EAAE,kBAAkB,CAAC,kCAAkC,CAAC;SAC7D,CAAC;KACH,CAAC;IACF,CAAC,kBAAkB,CAAC,aAAa,CAAC,EAAE;QAClC,OAAO,EAAE;YACP,MAAM,CAAC,EAAE;gBACP,MAAM,EAAE,MAAM,CAAC;gBACf,IAAI,EAAE,MAAM,CAAC;gBACb,MAAM,EAAE,MAAM,EAAE,CAAC;gBACjB,IAAI,CAAC,EAAE,MAAM,CAAC;aACf,CAAC;YACF,OAAO,CAAC,EAAE;gBACR,MAAM,EAAE,MAAM,CAAC;gBACf,IAAI,EAAE,MAAM,CAAC;gBACb,MAAM,EAAE,MAAM,EAAE,CAAC;gBACjB,IAAI,CAAC,EAAE,MAAM,CAAC;aACf,EAAE,CAAC;SACL,CAAC;QACF,QAAQ,EAAE,OAAO,CAAC;KACnB,CAAC;IACF,CAAC,kBAAkB,CAAC,SAAS,CAAC,EAAE;QAC9B,OAAO,EAAE;YACP,MAAM,EAAE;gBACN,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;gBAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;aACvB,CAAC;YACF,UAAU,CAAC,EAAE,MAAM,CAAC;YACpB,aAAa,CAAC,EAAE,OAAO,CAAC;YACxB,KAAK,CAAC,EAAE,MAAM,CAAC;SAChB,CAAC;QACF,QAAQ,EAAE,OAAO,CAAC;KACnB,CAAC;CACH,CAAC;AAGF,MAAM,MAAM,oBAAoB,CAAC,CAAC,SACxB,MAAM,yBAAyB,IACrC,yBAAyB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;AAE5C,MAAM,MAAM,qBAAqB,CAC/B,CAAC,SAAS,MAAM,yBAAyB,IACvC,yBAAyB,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;AAE7C,MAAM,MAAM,0BAA0B,CAAC,OAAO,SAAS,MAAM,yBAAyB,IACpF,KAAK,CAAC;IACJ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,qBAAqB,CAAC,OAAO,CAAC,CAAC;IACvC,KAAK,CAAC,EAAE,GAAG,CAAC;CACb,CAAC,CAAA;AAGJ,MAAM,MAAM,wBAAwB,GAAG;IACrC,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,kBAAkB,CAAC,oBAAoB,CAAC;IACzD,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC;IACtD,CAAC,SAAS,CAAC,gBAAgB,CAAC,EAAE,kBAAkB,CAAC,gBAAgB,CAAC;IAClE,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC;IACtD,CAAC,SAAS,CAAC,YAAY,CAAC,EAAE,kBAAkB,CAAC,YAAY,CAAC;IAC1D,CAAC,SAAS,CAAC,aAAa,CAAC,EAAE,kBAAkB,CAAC,aAAa,CAAC;IAC5D,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,kBAAkB,CAAC,MAAM,CAAC;IAC9C,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,kBAAkB,CAAC,OAAO,CAAC;IAChD,CAAC,SAAS,CAAC,kCAAkC,CAAC,EAAE,kBAAkB,CAAC,kCAAkC,CAAC;CACvG,CAAA;AAGD,MAAM,MAAM,gBAAgB,CAAC,UAAU,SAAS,SAAS,IACvD,UAAU,SAAS,MAAM,wBAAwB,GAC7C,oBAAoB,CAAC,wBAAwB,CAAC,UAAU,CAAC,CAAC,GAC1D,GAAG,CAAC;AAEV,MAAM,MAAM,iBAAiB,CAAC,UAAU,SAAS,SAAS,EAAE,QAAQ,SAAS,WAAW,IACtF,UAAU,SAAS,MAAM,wBAAwB,GAC7C,qBAAqB,CAAC,wBAAwB,CAAC,UAAU,CAAC,CAAC,GAC3D,GAAG,CAAC;AAGV,MAAM,MAAM,cAAc,CAAC,QAAQ,EAAE,UAAU,SAAS,SAAS,IAC/D,QAAQ,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;AAC1C,MAAM,MAAM,eAAe,CAAC,QAAQ,EAAE,UAAU,SAAS,SAAS,EAAE,QAAQ,SAAS,WAAW,IAC9F,QAAQ,SAAS,gBAAgB,CAAC,UAAU,CAAC,GAAG,iBAAiB,CAAC,UAAU,EAAE,QAAQ,CAAC,GAAG,GAAG,CAAC"} \ No newline at end of file diff --git a/dist/src/embed/hostEventClient/host-event-client.d.ts b/dist/src/embed/hostEventClient/host-event-client.d.ts new file mode 100644 index 00000000..5ebf1203 --- /dev/null +++ b/dist/src/embed/hostEventClient/host-event-client.d.ts @@ -0,0 +1,53 @@ +import { ContextType, HostEvent } from '../../types'; +import { UIPassthroughArrayResponse, UIPassthroughEvent, HostEventRequest, HostEventResponse, UIPassthroughRequest, UIPassthroughResponse, TriggerPayload, TriggerResponse } from './contracts'; +export declare class HostEventClient { + iFrame: HTMLIFrameElement; + /** Cached list of available UI passthrough keys from the embedded app */ + private availablePassthroughKeysCache; + /** Host events with custom handlers + * (setters or special logic) - + * bound to instance for protected method access */ + private readonly customHandlers; + constructor(iFrame?: HTMLIFrameElement); + /** + * A wrapper over process trigger to + * @param {HostEvent} message Host event to send + * @param {any} data Data to send with the host event + * @returns {Promise} - the response from the process trigger + */ + protected processTrigger(message: HostEvent, data: any, context?: ContextType): Promise; + handleHostEventWithParam(apiName: UIPassthroughEventT, parameters: UIPassthroughRequest, context?: ContextType): Promise>; + hostEventFallback(hostEvent: HostEvent, data: any, context?: ContextType): Promise; + /** + * For getter events that return data. Tries UI passthrough first; + * if the app doesn't support it (no response data), falls back to + * the legacy host event channel. Real errors are thrown as-is. + */ + private getDataWithPassthroughFallback; + /** + * Setter for the iframe element used for host events + * @param {HTMLIFrameElement} iFrame - the iframe element to set + */ + setIframeElement(iFrame: HTMLIFrameElement): void; + /** + * Fetches the list of available UI passthrough keys from the embedded app. + * Result is cached for the session. Returns empty array on failure. + */ + private getAvailableUIPassthroughKeys; + triggerUIPassthroughApi(apiName: UIPassthroughEventT, parameters: UIPassthroughRequest, context?: ContextType): Promise>; + protected handlePinEvent(payload: HostEventRequest, context?: ContextType): Promise>; + protected handleSaveAnswerEvent(payload: HostEventRequest, context?: ContextType): Promise; + protected handleUpdateFiltersEvent(payload: HostEventRequest, context?: ContextType): Promise; + protected handleDrillDownEvent(payload: HostEventRequest, context?: ContextType): Promise; + /** + * Dispatches a host event using the appropriate channel: + * 1. If the embedded app supports UI passthrough for this event, use it (custom handler or getter). + * 2. Otherwise fall back to the legacy host event channel. + * + * @param hostEvent - The host event to trigger + * @param payload - Optional payload for the event + * @param context - Optional context (e.g. vizId) for scoped operations + */ + triggerHostEvent(hostEvent: HostEventT, payload?: TriggerPayload, context?: ContextT): Promise>; +} +//# sourceMappingURL=host-event-client.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/hostEventClient/host-event-client.d.ts.map b/dist/src/embed/hostEventClient/host-event-client.d.ts.map new file mode 100644 index 00000000..d1003a2b --- /dev/null +++ b/dist/src/embed/hostEventClient/host-event-client.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"host-event-client.d.ts","sourceRoot":"","sources":["host-event-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AASrD,OAAO,EACH,0BAA0B,EAC1B,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,qBAAqB,EACrB,cAAc,EACd,eAAe,EAClB,MAAM,aAAa,CAAC;AAuBrB,qBAAa,eAAe;IAC1B,MAAM,EAAE,iBAAiB,CAAC;IAE1B,yEAAyE;IACzE,OAAO,CAAC,6BAA6B,CAAyB;IAE9D;;uDAEmD;IACnD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAE7B;gBAEU,MAAM,CAAC,EAAE,iBAAiB;IAUtC;;;;;OAKG;cACa,cAAc,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC;IAerF,wBAAwB,CAAC,mBAAmB,SAAS,kBAAkB,EAChF,OAAO,EAAE,mBAAmB,EAC5B,UAAU,EAAE,oBAAoB,CAAC,mBAAmB,CAAC,EACrD,OAAO,CAAC,EAAE,WAAW,GACtB,OAAO,CAAC,qBAAqB,CAAC,mBAAmB,CAAC,CAAC;IAqBzC,iBAAiB,CAC1B,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,GAAG,EACT,OAAO,CAAC,EAAE,WAAW,GACtB,OAAO,CAAC,GAAG,CAAC;IAIf;;;;OAIG;YACW,8BAA8B;IAyB5C;;;OAGG;IACI,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI;IAIxD;;;OAGG;YACW,6BAA6B;IAiB9B,uBAAuB,CAAC,mBAAmB,SAAS,kBAAkB,EAC/E,OAAO,EAAE,mBAAmB,EAC5B,UAAU,EAAE,oBAAoB,CAAC,mBAAmB,CAAC,EACrD,OAAO,CAAC,EAAE,WAAW,GACtB,OAAO,CAAC,0BAA0B,CAAC,mBAAmB,CAAC,CAAC;cAS3C,cAAc,CAC1B,OAAO,EAAE,gBAAgB,CAAC,SAAS,CAAC,GAAG,CAAC,EACxC,OAAO,CAAC,EAAE,WAAW,GACtB,OAAO,CAAC,iBAAiB,CAAC,SAAS,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;cAsBzC,qBAAqB,CACjC,OAAO,EAAE,gBAAgB,CAAC,SAAS,CAAC,UAAU,CAAC,EAC/C,OAAO,CAAC,EAAE,WAAW,GACtB,OAAO,CAAC,GAAG,CAAC;IAgBf,SAAS,CAAC,wBAAwB,CAChC,OAAO,EAAE,gBAAgB,CAAC,SAAS,CAAC,aAAa,CAAC,EAClD,OAAO,CAAC,EAAE,WAAW,GACpB,OAAO,CAAC,GAAG,CAAC;IAQf,SAAS,CAAC,oBAAoB,CAC5B,OAAO,EAAE,gBAAgB,CAAC,SAAS,CAAC,SAAS,CAAC,EAC9C,OAAO,CAAC,EAAE,WAAW,GACpB,OAAO,CAAC,GAAG,CAAC;IAQf;;;;;;;;OAQG;IACU,gBAAgB,CAC3B,UAAU,SAAS,SAAS,EAC5B,QAAQ,EACR,QAAQ,SAAS,WAAW,EAE1B,SAAS,EAAE,UAAU,EACrB,OAAO,CAAC,EAAE,cAAc,CAAC,QAAQ,EAAE,UAAU,CAAC,EAC9C,OAAO,CAAC,EAAE,QAAQ,GACnB,OAAO,CAAC,eAAe,CAAC,QAAQ,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;CAkB/D"} \ No newline at end of file diff --git a/dist/src/embed/hostEventClient/host-event-client.spec.d.ts b/dist/src/embed/hostEventClient/host-event-client.spec.d.ts new file mode 100644 index 00000000..ed4fe9a4 --- /dev/null +++ b/dist/src/embed/hostEventClient/host-event-client.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=host-event-client.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/hostEventClient/host-event-client.spec.d.ts.map b/dist/src/embed/hostEventClient/host-event-client.spec.d.ts.map new file mode 100644 index 00000000..480d56dc --- /dev/null +++ b/dist/src/embed/hostEventClient/host-event-client.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"host-event-client.spec.d.ts","sourceRoot":"","sources":["host-event-client.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/embed/hostEventClient/utils.d.ts b/dist/src/embed/hostEventClient/utils.d.ts new file mode 100644 index 00000000..ebcece43 --- /dev/null +++ b/dist/src/embed/hostEventClient/utils.d.ts @@ -0,0 +1,22 @@ +import { EmbedErrorCodes, EmbedEvent, ErrorDetailsTypes, HostEvent } from '../../types'; +import { HostEventRequest } from './contracts'; +import { embedEventStatus } from '../../utils'; +export declare function isValidUpdateFiltersPayload(payload: HostEventRequest | undefined): boolean; +export declare function isValidDrillDownPayload(payload: HostEventRequest | undefined): boolean; +export type ValidationError = Error & { + isValidationError?: boolean; + embedErrorDetails?: { + type: EmbedEvent.Error; + data: { + errorType: ErrorDetailsTypes; + message: string; + code: EmbedErrorCodes; + error: string; + }; + status: typeof embedEventStatus.END; + }; +}; +export declare function createValidationError(message: string): never; +export declare function throwUpdateFiltersValidationError(): never; +export declare function throwDrillDownValidationError(): never; +//# sourceMappingURL=utils.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/hostEventClient/utils.d.ts.map b/dist/src/embed/hostEventClient/utils.d.ts.map new file mode 100644 index 00000000..c7bb3c51 --- /dev/null +++ b/dist/src/embed/hostEventClient/utils.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExF,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,gBAAgB,CAAC,SAAS,CAAC,aAAa,CAAC,GAAG,SAAS,GAC7D,OAAO,CAuBT;AAED,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,gBAAgB,CAAC,SAAS,CAAC,SAAS,CAAC,GAAG,SAAS,GACzD,OAAO,CAUT;AAED,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG;IACpC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,iBAAiB,CAAC,EAAE;QAAE,IAAI,EAAE,UAAU,CAAC,KAAK,CAAC;QAAC,IAAI,EAAE;YAAE,SAAS,EAAE,iBAAiB,CAAC;YAAC,OAAO,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,eAAe,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,MAAM,EAAE,OAAO,gBAAgB,CAAC,GAAG,CAAA;KAAE,CAAC;CACpL,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,CAc5D;AAED,wBAAgB,iCAAiC,IAAI,KAAK,CAEzD;AAED,wBAAgB,6BAA6B,IAAI,KAAK,CAErD"} \ No newline at end of file diff --git a/dist/src/embed/hostEventClient/utils.spec.d.ts b/dist/src/embed/hostEventClient/utils.spec.d.ts new file mode 100644 index 00000000..d677f33d --- /dev/null +++ b/dist/src/embed/hostEventClient/utils.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=utils.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/hostEventClient/utils.spec.d.ts.map b/dist/src/embed/hostEventClient/utils.spec.d.ts.map new file mode 100644 index 00000000..c87e40d7 --- /dev/null +++ b/dist/src/embed/hostEventClient/utils.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"utils.spec.d.ts","sourceRoot":"","sources":["utils.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/embed/liveboard.d.ts b/dist/src/embed/liveboard.d.ts new file mode 100644 index 00000000..a8e1d7cd --- /dev/null +++ b/dist/src/embed/liveboard.d.ts @@ -0,0 +1,657 @@ +/** + * Copyright (c) 2022 + * + * Embed a ThoughtSpot Liveboard or visualization + * https://developers.thoughtspot.com/docs/embed-liveboard + * https://developers.thoughtspot.com/docs/embed-a-viz + * @summary Liveboard & visualization embed + * @author Ayon Ghosh + */ +import { DOMSelector, HostEvent, SearchLiveboardCommonViewConfig as LiveboardOtherViewConfig, BaseViewConfig, LiveboardAppEmbedViewConfig, ContextType, DefaultAppInitData } from '../types'; +import { TsEmbed, V1Embed } from './ts-embed'; +import { TriggerPayload, TriggerResponse } from './hostEventClient/contracts'; +import { SpotterChatViewConfig } from './conversation'; +import { SpotterVizConfig } from './spotter-viz-utils'; +/** + * APP_INIT data shape for LiveboardEmbed. + * @internal + */ +export interface LiveboardEmbedAppInitData extends DefaultAppInitData { + embedParams?: { + spotterVizConfig?: SpotterVizConfig; + }; +} +/** + * The configuration for the embedded Liveboard or visualization page view. + * @group Embed components + */ +export interface LiveboardViewConfig extends BaseViewConfig, LiveboardOtherViewConfig, LiveboardAppEmbedViewConfig { + /** + * If set to true, the embedded object container dynamically resizes + * according to the height of the Liveboard. + * + * **Note**: Using fullHeight loads all visualizations on the + * Liveboard simultaneously, which results in multiple warehouse + * queries and potentially a longer wait for the topmost + * visualizations to display on the screen. + * Setting `fullHeight` to `false` fetches visualizations + * incrementally as users scroll the page to view the charts and tables. + * + * + * Supported embed types: `LiveboardEmbed` + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 7.2.1 + * @example + * ```js + * const embed = new LiveboardEmbed('#embed', { + * ... // other liveboard view config + * fullHeight: true, + * }); + * ``` + */ + fullHeight?: boolean; + /** + * This is the minimum height (in pixels) for a full-height Liveboard. + * Setting this height helps resolve issues with empty Liveboards and + * other screens navigable from a Liveboard. + * + * Supported embed types: `LiveboardEmbed` + * @version SDK: 1.5.0 | ThoughtSpot: ts7.oct.cl, 7.2.1 + * @deprecated Use `minimumHeight` instead. + * @default 500 + * @example + * ```js + * const embed = new LiveboardEmbed('#embed', { + * ... // other liveboard view config + * fullHeight: true, + * defaultHeight: 600, + * }); + * ``` + */ + defaultHeight?: number; + /** + * This is the minimum height (in pixels) for a full-height Liveboard. + * Setting this height helps resolve issues with empty Liveboards and + * other screens navigable from a Liveboard. + * + * @version SDK: 1.44.2 | ThoughtSpot: 10.15.0.cl + * @default 500 + * @example + * ```js + * const embed = new LiveboardEmbed('#embed', { + * ... // other liveboard view config + * fullHeight: true, + * minimumHeight: 600, + * }); + * ``` + */ + minimumHeight?: number; + /** + * If set to true, the context menu in visualizations will be enabled. + * @version SDK: 1.1.0 | ThoughtSpot: 8.1.0.sw + * @deprecated this option is deprecated. + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... //other embed view config + * enableVizTransformations:true, + * }) + * ``` + */ + enableVizTransformations?: boolean; + /** + * The Liveboard to display in the embedded view. + * Use either liveboardId or pinboardId to reference the Liveboard to embed. + * + * Supported embed types: `LiveboardEmbed` + * @version SDK: 1.3.0 | ThoughtSpot ts7.aug.cl, 7.2.1 + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... //other embed view config + * liveboardId:'id of liveboard', + * }) + * ``` + */ + liveboardId?: string; + /** + * To support backward compatibility + * @hidden + */ + pinboardId?: string; + /** + * The visualization within the Liveboard to display. + * + * Supported embed types: `LiveboardEmbed` + * @version SDK: 1.9.1 | ThoughtSpot: 8.1.0.cl, 8.4.1-sw + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... //other embed view config + * vizId:'430496d6-6903-4601-937e-2c691821af3c', + * }) + * ``` + */ + vizId?: string; + /** + * If set to true, all filter chips from a + * Liveboard page will be read-only (no X buttons) + * + * Supported embed types: `LiveboardEmbed` + * @version SDK: 1.3.0 | ThoughtSpot ts7.aug.cl, 7.2.1.sw + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... //other embed view config + * preventLiveboardFilterRemoval:true, + * }) + * ``` + */ + preventLiveboardFilterRemoval?: boolean; + /** + * Array of visualization IDs which should be visible when the Liveboard + * renders. This can be changed by triggering the `SetVisibleVizs` + * event. + * + * Supported embed types: `LiveboardEmbed` + * @version SDK: 1.9.1 | ThoughtSpot: 8.1.0.cl, 8.4.1-sw + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... //other embed view config + * visibleVizs: [ + * '430496d6-6903-4601-937e-2c691821af3c', + * 'f547ec54-2a37-4516-a222-2b06719af726' + * ] + * }) + * ``` + */ + visibleVizs?: string[]; + /** + * To support backward compatibility + * @hidden + */ + preventPinboardFilterRemoval?: boolean; + /** + * Render embedded Liveboards and visualizations in the + * new Liveboard experience mode. + * + * Supported embed types: `LiveboardEmbed` + * @version SDK: 1.14.0 | ThoughtSpot: 8.6.0.cl, 8.8.1-sw + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... //other embed view config + * liveboardV2:true, + * }) + * ``` + */ + liveboardV2?: boolean; + /** + * Set a Liveboard tab as an active tab. + * Specify the tab ID. + * + * Supported embed types: `LiveboardEmbed` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1-sw + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... //other embed view config + * activeTabId:'id-1234', + * }) + * ``` + */ + activeTabId?: string; + /** + * The GUID of a saved personalized view to load. + * A personalized view is a saved configuration of a Liveboard + * that includes specific filter selections. + * + * Supported embed types: `LiveboardEmbed` + * @version SDK: 1.46.0 | ThoughtSpot: 26.4.0.cl + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * liveboardId: 'liveboard-guid', + * personalizedViewId: 'view-guid', + * activeTabId: 'tab-guid', + * }) + * ``` + */ + personalizedViewId?: string; + /** + * Show or hide the tab panel of the embedded Liveboard. + * + * Supported embed types: `LiveboardEmbed` + * @version SDK: 1.25.0 | ThoughtSpot: 9.6.0.cl, 9.8.0.sw + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... //other embed view config + * hideTabPanel:true, + * }) + * ``` + */ + hideTabPanel?: boolean; + /** + * Show a preview image of the visualization before the visualization loads. + * Only works for visualizations embeds with a viz id. + * + * Also, viz snapshot should be enabled in the ThoughtSpot instance. + * Contact ThoughtSpot support to enable this feature. + * + * Since this will show preview images, be careful that it may show + * undesired data to the user when using row level security. + * + * Supported embed types: `LiveboardEmbed` + * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * liveboardId: 'liveboard-id', + * vizId: 'viz-id', + * showPreviewLoader: true, + * }); + * embed.render(); + * ``` + */ + showPreviewLoader?: boolean; + /** + * The Liveboard to run on regular intervals to fetch the cdw token. + * + * Supported embed types: `LiveboardEmbed` + * @hidden + * @version SDK: 1.35.0 | ThoughtSpot: 10.6.0.cl + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... //other embed view config + * oAuthPollingInterval: 30000, + * }) + * ``` + */ + oAuthPollingInterval?: number; + /** + * The Liveboard is set to force a token fetch during the initial load. + * + * Supported embed types: `LiveboardEmbed` + * @hidden + * @version SDK: 1.35.0 | ThoughtSpot: 10.6.0.cl + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... //other embed view config + * isForceRedirect: false, + * }) + * ``` + */ + isForceRedirect?: boolean; + /** + * The source connection ID for authentication. + * + * Supported embed types: `LiveboardEmbed` + * @hidden + * @version SDK: 1.35.0 | ThoughtSpot: 10.6.0.cl + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... //other embed view config + * dataSourceId: '', + * }) + * ``` + */ + dataSourceId?: string; + /** + * The list of tab IDs to hide from the embedded Liveboard. + * These tabs will be hidden from their respective Liveboards. + * Use this to hide a tab ID. + * + * Supported embed types: `LiveboardEmbed` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 10.1.0.sw + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... // other embed view config + * hiddenTabs: [ + * '430496d6-6903-4601-937e-2c691821af3c', + * 'f547ec54-2a37-4516-a222-2b06719af726' + * ] + * }); + * ``` + */ + hiddenTabs?: string[]; + /** + * The list of tab IDs to show in the embedded Liveboard. + * Only the tabs specified in the array will be shown in the Liveboard. + * + * Use either `visibleTabs` or `hiddenTabs`. + * + * Supported embed types: `LiveboardEmbed` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 10.1.0.sw + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... // other embed view config + * visibleTabs: [ + * '430496d6-6903-4601-937e-2c691821af3c', + * 'f547ec54-2a37-4516-a222-2b06719af726' + * ] + * }) + * ``` + */ + visibleTabs?: string[]; + /** + * This flag is used to enable/disable the styling and grouping in a Liveboard. Use {@link isLiveboardMasterpiecesEnabled} instead. + * @deprecated This flag is deprecated. + * + * Supported embed types: `LiveboardEmbed`, `AppEmbed` + * @type {boolean} + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * isLiveboardStylingAndGroupingEnabled: true, + * }) + * ``` + */ + isLiveboardStylingAndGroupingEnabled?: boolean; + /** + * This flag is used to enable/disable the png embedding of liveboard in scheduled + * mails + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @type {boolean} + * @version SDK: 1.42.0 | ThoughtSpot: 10.14.0.cl + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * isPNGInScheduledEmailsEnabled: true, + * }) + * ``` + */ + isPNGInScheduledEmailsEnabled?: boolean; + /** + * Enables the 'what you see is what you get' PDF export for Liveboards. Each tab is rendered on a single page + * following the exact UI layout, instead of splitting visualizations across multiple A4 pages. + * This feature is GA from version 26.5.0.cl. It is disabled by default in embed deployments. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @type {boolean} + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * isContinuousLiveboardPDFEnabled: true, + * }) + * ``` + */ + isContinuousLiveboardPDFEnabled?: boolean; + /** + * This flag is used to enable/disable the XLSX/CSV download option for Liveboards + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @type {boolean} + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * isLiveboardXLSXCSVDownloadEnabled: true, + * }) + * ``` + */ + isLiveboardXLSXCSVDownloadEnabled?: boolean; + /** + * This flag is used to enable/disable the granular XLSX/CSV schedules feature + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @type {boolean} + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * isGranularXLSXCSVSchedulesEnabled: true, + * }) + * ``` + */ + isGranularXLSXCSVSchedulesEnabled?: boolean; + /** + * This flag is used to enable the full height lazy load data. + * + * @type {boolean} + * @version SDK: 1.40.0 | ThoughtSpot: 10.12.0.cl + * @default false + * @example + * ```js + * const embed = new LiveboardEmbed('#embed-container', { + * // ...other options + * fullHeight: true, + * lazyLoadingForFullHeight: true, + * }) + * ``` + */ + lazyLoadingForFullHeight?: boolean; + /** + * The margin to be used for lazy loading. + * + * For example, if the margin is set to '10px', + * the visualization will be loaded 10px before its top edge is visible in the + * viewport. + * + * The format is similar to CSS margin. + * + * @type {string} + * @version SDK: 1.40.0 | ThoughtSpot: 10.12.0.cl + * @example + * ```js + * const embed = new LiveboardEmbed('#embed-container', { + * // ...other options + * fullHeight: true, + * lazyLoadingForFullHeight: true, + * // Using 0px, the visualization will be only loaded when it's visible in the viewport. + * lazyLoadingMargin: '0px', + * }) + * ``` + */ + lazyLoadingMargin?: string; + /** + * showSpotterLimitations : show limitation text + * of the spotter underneath the chat input. + * default is false. + * + * @type {boolean} + * @version SDK: 1.41.1 | ThoughtSpot: 10.5.0.cl + * @example + * ```js + * const embed = new LiveboardEmbed('#embed-container', { + * // ...other options + * showSpotterLimitations: true, + * }) + * ``` + */ + showSpotterLimitations?: boolean; + /** + * updatedSpotterChatPrompt : Controls the updated spotter chat prompt. + * + * Supported embed types: `LiveboardEmbed` + * @version SDK: 1.45.0 | ThoughtSpot: 26.2.0.cl + * @default false + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... //other embed view config + * updatedSpotterChatPrompt : true, + * }) + * ``` + */ + updatedSpotterChatPrompt?: boolean; + /** + * Enables the stop answer generation button in the Spotter embed UI, + * allowing users to interrupt an ongoing answer generation. + * + * Supported embed types: `LiveboardEmbed` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + * @default false + */ + enableStopAnswerGenerationEmbed?: boolean; + /** + * Configuration for customizing Spotter chat UI + * branding in tool response cards. + * + * Supported embed types: `LiveboardEmbed` + * @version SDK: 1.46.0 | ThoughtSpot: 26.4.0.cl + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... //other embed view config + * spotterChatConfig: { + * hideToolResponseCardBranding: true, + * toolResponseCardBrandingLabel: 'MyBrand', + * }, + * }) + * ``` + */ + spotterChatConfig?: SpotterChatViewConfig; + /** + * Configuration for the SpotterViz interface shown on the Liveboard. + * Customize the brand name, description, chat input placeholder, + * starter prompts, and visibility of starter prompts in the SpotterViz panel. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * const embed = new LiveboardEmbed('#embed-container', { + * ... // other options + * spotterViz: { + * brandName: 'MyBrand', + * brandHeadline: 'Hi, there! I\'m', + * description: 'Ask questions about your data', + * inputChatPlaceholder: 'Ask a question...', + * hideStarterPrompts: false, + * customStarterPrompts: [{ id: '1', displayText: 'Top products', fullPrompt: 'What are the top products by revenue?' }], + * }, + * }) + * ``` + */ + spotterViz?: SpotterVizConfig; + /** + * If set to true, enables visualization data caching on the Liveboard. + * @type {boolean} + * @version SDK: 1.49.0 | ThoughtSpot: 26.6.0.cl + * @example + * ```js + * const embed = new LiveboardEmbed('#embed-container', { + * ... // other options + * enableLiveboardDataCache: true, + * }) + * ``` + */ + enableLiveboardDataCache?: boolean; +} +/** + * Embed a ThoughtSpot Liveboard or visualization. When rendered it already + * waits for the authentication to complete, so you need not wait for + * `AuthStatus.SUCCESS`. + * @group Embed components + * @example + * ```js + * import { .. } from '@thoughtspot/visual-embed-sdk'; + * init({ ... }); + * const embed = new LiveboardEmbed("#container", { + * liveboardId: , + * // .. other params here. + * }) + * ``` + */ +export declare class LiveboardEmbed extends V1Embed { + protected viewConfig: LiveboardViewConfig; + private defaultHeight; + constructor(domSelector: DOMSelector, viewConfig: LiveboardViewConfig); + protected getAppInitData(): Promise; + /** + * Construct a map of params to be passed on to the + * embedded Liveboard or visualization. + */ + protected getEmbedParams(): string; + protected getEmbedParamsObject(): any; + private getIframeSuffixSrc; + private sendFullHeightLazyLoadData; + /** + * This is a handler for the RequestVisibleEmbedCoordinates event. + * It is used to send the visible coordinates data to the host application. + * @param data The event payload + * @param responder The responder function + */ + private requestVisibleEmbedCoordinatesHandler; + /** + * Construct the URL of the embedded ThoughtSpot Liveboard or visualization + * to be loaded within the iFrame. + */ + private getIFrameSrc; + /** + * Set the iframe height as per the computed height received + * from the ThoughtSpot app. + * @param data The event payload + */ + private updateIFrameHeight; + private embedIframeCenter; + private setIframeHeightForNonEmbedLiveboard; + private setActiveTab; + private showPreviewLoader; + /** + * @hidden + * Internal state to track the current liveboard id. + * This is used to navigate to the correct liveboard when the prerender is visible. + */ + currentLiveboardState: { + liveboardId: string; + vizId: string; + activeTabId: string; + personalizedViewId: string; + }; + protected beforePrerenderVisible(): void; + protected handleRenderForPrerender(): Promise; + /** + * Triggers an event to the embedded app + * @param {HostEvent} messageType The event type + * @param {any} data The payload to send with the message + * @returns A promise that resolves with the response from the embedded app + */ + trigger(messageType: HostEventT, data?: TriggerPayload, context?: ContextT): Promise>; + /** + * Destroys the ThoughtSpot embed, and remove any nodes from the DOM. + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + destroy(): void; + private postRender; + private registerLazyLoadEvents; + private unregisterLazyLoadEvents; + /** + * Render an embedded ThoughtSpot Liveboard or visualization + * @param renderOptions An object specifying the Liveboard ID, + * visualization ID and the runtime filters. + */ + render(): Promise; + navigateToLiveboard(liveboardId: string, vizId?: string, activeTabId?: string, personalizedViewId?: string): void; + /** + * Returns the full url of the Liveboard/visualization which can be used to open + * this Liveboard inside the full ThoughtSpot application in a new tab. + * @returns url string + */ + getLiveboardUrl(): string; +} +/** + * @hidden + */ +export declare class PinboardEmbed extends LiveboardEmbed { +} +//# sourceMappingURL=liveboard.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/liveboard.d.ts.map b/dist/src/embed/liveboard.d.ts.map new file mode 100644 index 00000000..8d6f8696 --- /dev/null +++ b/dist/src/embed/liveboard.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"liveboard.d.ts","sourceRoot":"","sources":["liveboard.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,EAKH,WAAW,EACX,SAAS,EACT,+BAA+B,IAAI,wBAAwB,EAC3D,cAAc,EACd,2BAA2B,EAG3B,WAAW,EACX,kBAAkB,EACrB,MAAM,UAAU,CAAC;AAGlB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAE9E,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAA8B,MAAM,qBAAqB,CAAC;AAEnF;;;GAGG;AACH,MAAM,WAAW,yBAA0B,SAAQ,kBAAkB;IACjE,WAAW,CAAC,EAAE;QACV,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;KACvC,CAAC;CACL;AAGD;;;GAGG;AACH,MAAM,WAAW,mBAAoB,SAAQ,cAAc,EAAE,wBAAwB,EAAE,2BAA2B;IAC9G;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;;;;;;;;;;OAiBG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;;;;;OAeG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;OAWG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC;;;;;;;;;;;;;OAaG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;;;;;;;OAaG;IACH,6BAA6B,CAAC,EAAE,OAAO,CAAC;IACxC;;;;;;;;;;;;;;;;;OAiBG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB;;;OAGG;IACH,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC;;;;;;;;;;;;;OAaG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;;;;;;;;;OAaG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;;;;;OAeG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;;;;;;OAYG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;;;;OAaG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B;;;;;;;;;;;;;OAaG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B;;;;;;;;;;;;;OAaG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;;;;;;;;OAiBG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB;;;;;;;;;;;;;;;;;;OAkBG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB;;;;;;;;;;;;;;;OAeG;IACH,oCAAoC,CAAC,EAAE,OAAO,CAAC;IAC/C;;;;;;;;;;;;;;;OAeG;IACH,6BAA6B,CAAC,EAAE,OAAO,CAAC;IACxC;;;;;;;;;;;;;;;;OAgBG;IACH,+BAA+B,CAAC,EAAE,OAAO,CAAC;IAC1C;;;;;;;;;;;;;;OAcG;IACH,iCAAiC,CAAC,EAAE,OAAO,CAAC;IAC5C;;;;;;;;;;;;;;OAcG;IACH,iCAAiC,CAAC,EAAE,OAAO,CAAC;IAC5C;;;;;;;;;;;;;;OAcG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;;;;;;;;;;OAcG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;;;OAaG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC;;;;;;;OAOG;IACH,+BAA+B,CAAC,EAAE,OAAO,CAAC;IAC1C;;;;;;;;;;;;;;;;OAgBG;IACH,iBAAiB,CAAC,EAAE,qBAAqB,CAAC;IAC1C;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B;;;;;;;;;;;OAWG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACtC;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,cAAe,SAAQ,OAAO;IACvC,SAAS,CAAC,UAAU,EAAE,mBAAmB,CAAC;IAE1C,OAAO,CAAC,aAAa,CAAO;gBAGhB,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,mBAAmB;cAgBrD,cAAc,IAAI,OAAO,CAAC,yBAAyB,CAAC;IAKpE;;;OAGG;IACH,SAAS,CAAC,cAAc;IAMxB,SAAS,CAAC,oBAAoB;IAoM9B,OAAO,CAAC,kBAAkB;IAwC1B,OAAO,CAAC,0BAA0B,CAMhC;IAEF;;;;;OAKG;IACH,OAAO,CAAC,qCAAqC,CAI5C;IAED;;;OAGG;IACH,OAAO,CAAC,YAAY;IAoBpB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB,CAGxB;IAEF,OAAO,CAAC,iBAAiB,CAGvB;IAEF,OAAO,CAAC,mCAAmC,CAqBzC;IAEF,OAAO,CAAC,YAAY;YAQN,iBAAiB;IAkC/B;;;;OAIG;IACI,qBAAqB;;;;;MAK1B;IAEF,SAAS,CAAC,sBAAsB,IAAI,IAAI;cAsBxB,wBAAwB,IAAI,OAAO,CAAC,OAAO,CAAC;IAO5D;;;;;OAKG;IACI,OAAO,CAAC,UAAU,SAAS,SAAS,EAAE,QAAQ,EAAE,QAAQ,SAAS,WAAW,EAC/E,WAAW,EAAE,UAAU,EACvB,IAAI,GAAE,cAAc,CAAC,QAAQ,EAAE,UAAU,CAAe,EACxD,OAAO,CAAC,EAAE,QAAQ,GACnB,OAAO,CAAC,eAAe,CAAC,QAAQ,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;IAW3D;;;OAGG;IACI,OAAO;IAKd,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,sBAAsB;IAQ9B,OAAO,CAAC,wBAAwB;IAOhC;;;;OAIG;IACU,MAAM,IAAI,OAAO,CAAC,cAAc,CAAC;IAWvC,mBAAmB,CACtB,WAAW,EAAE,MAAM,EACnB,KAAK,CAAC,EAAE,MAAM,EACd,WAAW,CAAC,EAAE,MAAM,EACpB,kBAAkB,CAAC,EAAE,MAAM;IAgB/B;;;;OAIG;IACI,eAAe,IAAI,MAAM;CAgBnC;AAED;;GAEG;AACH,qBAAa,aAAc,SAAQ,cAAc;CAAI"} \ No newline at end of file diff --git a/dist/src/embed/liveboard.spec.d.ts b/dist/src/embed/liveboard.spec.d.ts new file mode 100644 index 00000000..653be692 --- /dev/null +++ b/dist/src/embed/liveboard.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=liveboard.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/liveboard.spec.d.ts.map b/dist/src/embed/liveboard.spec.d.ts.map new file mode 100644 index 00000000..8f859256 --- /dev/null +++ b/dist/src/embed/liveboard.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"liveboard.spec.d.ts","sourceRoot":"","sources":["liveboard.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/embed/pinboard.spec.d.ts b/dist/src/embed/pinboard.spec.d.ts new file mode 100644 index 00000000..a254cc6e --- /dev/null +++ b/dist/src/embed/pinboard.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=pinboard.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/pinboard.spec.d.ts.map b/dist/src/embed/pinboard.spec.d.ts.map new file mode 100644 index 00000000..5a43d0df --- /dev/null +++ b/dist/src/embed/pinboard.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"pinboard.spec.d.ts","sourceRoot":"","sources":["pinboard.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/embed/search-bar.d.ts b/dist/src/embed/search-bar.d.ts new file mode 100644 index 00000000..714ca711 --- /dev/null +++ b/dist/src/embed/search-bar.d.ts @@ -0,0 +1,127 @@ +import { SearchLiveboardCommonViewConfig, BaseViewConfig, DefaultAppInitData } from '../types'; +import { TsEmbed } from './ts-embed'; +import { SearchOptions } from './search'; +/** + * @group Embed components + */ +export interface SearchBarViewConfig extends BaseViewConfig, SearchLiveboardCommonViewConfig { + /** + * The array of data source GUIDs to set on load. + * Only a single data source is supported currently. + * + * Supported embed types: `SearchBarEmbed` + * @version SDK: 1.1.0 | ThoughtSpot: 8.1.1-sw + * @deprecated Use `dataSource` instead + * @example + * ```js + * const embed = new SearchBarEmbed('#tsEmbed', { + * ... //other embed view config + * dataSources:['id-2345','id-2345'], + * }) + * ``` + */ + dataSources?: string[]; + /** + * Pass the ID of the source to be selected. + * + * Supported embed types: `SearchBarEmbed` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + * @example + * ```js + * const embed = new SearchBarEmbed('#tsEmbed', { + * ... //other embed view config + * dataSource:'id-2345', + * }) + * ``` + */ + dataSource?: string; + /** + * Boolean to define if the last selected data source should be used + * + * Supported embed types: `SearchBarEmbed` + * @version SDK: 1.24.0 | ThoughtSpot: 9.5.0.cl, 9.5.0.sw + * @example + * ```js + * const embed = new SearchBarEmbed('#tsEmbed', { + * ... //other embed view config + * useLastSelectedSources:false, + * }) + * ``` + */ + useLastSelectedSources?: boolean; + /** + * Configuration for search options. + * Includes the following properties: + * + * `searchTokenString`: Search tokens to pass in the query. + * + * `executeSearch`: Boolean to define if the search should be executed or not. + * If it is executed, the focus is placed on the results. + * If it’s not executed, the focus is placed at the end of + * the token string in the search bar. + * + * Supported embed types: `SearchBarEmbed` + * @version SDK: 1.2.0 | ThoughtSpot: 9.4.0.sw + * @example + * ```js + * const embed = new SearchBarEmbed('#tsEmbed', { + * ... //other embed view config + * searchOptions: { + * searchTokenString: '[quantity purchased] [region]', + * executeSearch: true, + * } + * }) + * ``` + */ + searchOptions?: SearchOptions; + /** + * Exclude the search token string from the URL. + * If set to true, the search token string is not appended to the URL. + * + * Supported embed types: `SearchBarEmbed` + * @version SDK: 1.35.7 | ThoughtSpot: 10.8.0.cl + * @example + * ```js + * const embed = new SearchBarEmbed('#tsEmbed', { + * searchOptions: { + * searchTokenString: '[quantity purchased] [region]', + * executeSearch: true, + * }, + * excludeSearchTokenStringFromURL: true, + * }); + * ``` + */ + excludeSearchTokenStringFromURL?: boolean; +} +export interface SearchAppInitData extends DefaultAppInitData { + searchOptions: SearchOptions; +} +/** + * Embed ThoughtSpot search bar + * @version SDK: 1.18.0 | ThoughtSpot: 8.10.0.cl, 9.0.1-sw + * @group Embed components + */ +export declare class SearchBarEmbed extends TsEmbed { + /** + * The view configuration for the embedded ThoughtSpot search bar. + */ + protected viewConfig: SearchBarViewConfig; + protected embedComponentType: string; + constructor(domSelector: string, viewConfig: SearchBarViewConfig); + protected getEmbedParamsObject(): Record; + /** + * Construct the URL of the embedded ThoughtSpot search to be + * loaded in the iframe + * @param dataSources A list of data source GUIDs + */ + private getIFrameSrc; + /** + * Render the embedded ThoughtSpot search + */ + render(): Promise; + protected getSearchInitData(): { + searchOptions: SearchOptions; + }; + protected getAppInitData(): Promise; +} +//# sourceMappingURL=search-bar.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/search-bar.d.ts.map b/dist/src/embed/search-bar.d.ts.map new file mode 100644 index 00000000..c52736e3 --- /dev/null +++ b/dist/src/embed/search-bar.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"search-bar.d.ts","sourceRoot":"","sources":["search-bar.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,+BAA+B,EAAE,cAAc,EAAE,kBAAkB,EAAS,MAAM,UAAU,CAAC;AAEtG,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,cAAc,EAAE,+BAA+B;IACxF;;;;;;;;;;;;;;OAcG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB;;;;;;;;;;;;OAYG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;;;;;;OAYG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;;;;;;;;;;;;;;;;OAgBG;IACH,+BAA+B,CAAC,EAAE,OAAO,CAAC;CAC7C;AAED,MAAM,WAAW,iBAAkB,SAAQ,kBAAkB;IACzD,aAAa,EAAE,aAAa,CAAC;CAChC;AAED;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,OAAO;IACvC;;OAEG;IACH,SAAS,CAAC,UAAU,EAAE,mBAAmB,CAAC;IAE1C,SAAS,CAAC,kBAAkB,SAAoB;gBAEpC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,mBAAmB;IAKhE,SAAS,CAAC,oBAAoB;IAuC9B;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAcpB;;OAEG;IACU,MAAM,IAAI,OAAO,CAAC,cAAc,CAAC;IAO9C,SAAS,CAAC,iBAAiB;;;cAQX,cAAc,IAAI,OAAO,CAAC,iBAAiB,CAAC;CAI/D"} \ No newline at end of file diff --git a/dist/src/embed/search.d.ts b/dist/src/embed/search.d.ts new file mode 100644 index 00000000..f60b92fa --- /dev/null +++ b/dist/src/embed/search.d.ts @@ -0,0 +1,359 @@ +/** + * Copyright (c) 2022 + * + * Embed ThoughtSpot search or a saved answer. + * https://developers.thoughtspot.com/docs/search-embed + * @summary Search embed + * @author Ayon Ghosh + */ +import { DOMSelector, Action, SearchLiveboardCommonViewConfig, DefaultAppInitData, BaseViewConfig, VisualizationOverrides } from '../types'; +import { TsEmbed } from './ts-embed'; +/** + * Configuration for search options. + * + */ +export interface SearchOptions { + /** + * Search tokens to pass in the query. + */ + searchTokenString: string; + /** + * Boolean to define if the search should be executed or not. + * If it is executed, the focus is placed on the results. + * If it’s not executed, the focus is placed at the end of + * the token string in the search bar. + */ + executeSearch?: boolean; +} +/** + * Define the initial state of column custom group accordions + * in data panel v2. + */ +export declare enum DataPanelCustomColumnGroupsAccordionState { + /** + * Expand all the accordion initially in data panel v2. + */ + EXPAND_ALL = "EXPAND_ALL", + /** + * Collapse all the accordions initially in data panel v2. + */ + COLLAPSE_ALL = "COLLAPSE_ALL", + /** + * Expand the first accordion and collapse the rest. + */ + EXPAND_FIRST = "EXPAND_FIRST" +} +/** + * The configuration attributes for the embedded search view. + * @group Embed components + */ +export interface SearchViewConfig extends SearchLiveboardCommonViewConfig, Omit { + /** + * If set to true, the data sources panel is collapsed on load, + * but can be expanded manually. + * + * Supported embed types: `SearchEmbed` + * @version SDK: 1.1.0 | ThoughtSpot: 8.1.0.sw + * @example + * ```js + * const embed = new SearchEmbed('#tsEmbed', { + * ... // other embed view config + * collapseDataSources:true, + * }) + * ``` + */ + collapseDataSources?: boolean; + /** + * If set to true, the data panel is collapsed on load, + * but can be expanded manually. + * + * Supported embed types: `SearchEmbed` + * @version SDK: 1.34.0 | ThoughtSpot: 10.3.0.cl + * @example + * ```js + * const embed = new SearchEmbed('#tsEmbed', { + * ... // other embed view config + * collapseDataPanel:true, + * }) + * ``` + */ + collapseDataPanel?: boolean; + /** + * Show or hide the data sources panel. + * + * Supported embed types: `SearchEmbed` + * @version SDK: 1.2.0 | ThoughtSpot: 9.1.0.sw + * @example + * ```js + * const embed = new SearchEmbed('#tsEmbed', { + * ... // other embed view config + * hideDataSources:true, + * }) + * ``` + */ + hideDataSources?: boolean; + /** + * Show or hide the charts and tables in search answers. + * This attribute can be used to create a custom visualization + * using raw answer data. + * + * Supported embed types: `SearchEmbed` + * @version SDK: 1.2.0 | ThoughtSpot: 9.1.0.sw + * @example + * ```js + * const embed = new SearchEmbed('#tsEmbed', { + * ... // other embed view config + * hideResults:true, + * }) + * ``` + */ + hideResults?: boolean; + /** + * If set to true, the Search Assist feature is enabled. + * + * Supported embed types: `SearchEmbed` + * @version SDK: 1.13.0 | ThoughtSpot: 8.5.0.cl, 8.8.1-sw + * @example + * ```js + * const embed = new SearchEmbed('#tsEmbed', { + * ... // other embed view config + * enableSearchAssist:true, + * }) + * ``` + */ + enableSearchAssist?: boolean; + /** + * If set to true, the tabular view is set as the default + * format for presenting search data. + * + * Supported embed types: `SearchEmbed` + * @version SDK: 1.1.0 | ThoughtSpot: 8.1.0.sw + * @example + * ```js + * const embed = new SearchEmbed('#tsEmbed', { + * ... // other embed view config + * forceTable:true, + * }) + * ``` + */ + forceTable?: boolean; + /** + * The array of data source GUIDs to set on load. + * Only a single data source is supported currently. + * Use {@link dataSource} instead. + * @deprecated Use `dataSource` instead. + * + * Supported embed types: `SearchEmbed` + * @example + * ```js + * const embed = new SearchEmbed('#tsEmbed', { + * ... // other embed view config + * dataSources:['id-234','id-456'], + * }) + * ``` + */ + dataSources?: string[]; + /** + * The data source GUID to set on load. + * + * Supported embed types: `SearchEmbed` + * @version SDK: 1.19.0 + * @example + * ```js + * const embed = new SearchEmbed('#tsEmbed', { + * ... // other embed view config + * dataSource:'id-234', + * }) + * ``` + */ + dataSource?: string; + /** + * The initial search query to load the answer with. + * Use {@link searchOptions} instead. + * @deprecated + * + */ + searchQuery?: string; + /** + * Configuration for search options. + * Includes the following properties: + * + * `searchTokenString`: Search tokens to pass in the query. + * + * `executeSearch`: Boolean to define if the search should be executed or not. + * If it is executed, the focus is placed on the results. + * If it’s not executed, the focus is placed at the end of + * the token string in the search bar. + * + * Supported embed types: `SearchEmbed` + * @example + * ```js + * searchOptions: { + * searchTokenString: '[quantity purchased] [region]', + * executeSearch: true, + * } + * ``` + */ + searchOptions?: SearchOptions; + /** + * Exclude the search token string from the URL. + * If set to true, the search token string is not appended to the URL. + * + * Supported embed types: `SearchEmbed` + * @version SDK: 1.35.7 | ThoughtSpot: 10.8.0.cl + * @example + * ```js + * const embed = new SearchEmbed('#tsEmbed', { + * searchOptions: { + * searchTokenString: '[quantity purchased] [region]', + * executeSearch: true, + * }, + * excludeSearchTokenStringFromURL: true, + * }); + * ``` + */ + excludeSearchTokenStringFromURL?: boolean; + /** + * The GUID of a saved answer to load initially. + * + * Supported embed types: `SearchEmbed` + * @version SDK: 1.1.0 | ThoughtSpot: 8.1.0.sw + * @example + * ```js + * const embed = new SearchEmbed('#tsEmbed', { + * ... // other embed view config + * answerId:'sed-1234', + * }) + * ``` + */ + answerId?: string; + /** + * If set to true, the search page will render without the Search Bar + * The chart/table should still be visible. + * + * Supported embed types: `SearchEmbed` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.0.sw + * @example + * ```js + * const embed = new SearchEmbed('#tsEmbed', { + * ... // other embed view config + * hideSearchBar:true, + * }) + * ``` + */ + hideSearchBar?: boolean; + /** + * Flag to set if last selected dataSource should be used + * + * Supported embed types: `SearchEmbed` + * @version SDK: 1.24.0 + */ + useLastSelectedSources?: boolean; + /** + * To set the initial state of the search bar in case of saved-answers. + * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl + * @deprecated Use {@link collapseSearchBar} instead + * @default false + */ + collapseSearchBarInitially?: boolean; + /** + * This controls the initial behaviour of custom column groups accordion. + * It takes DataPanelCustomColumnGroupsAccordionState enum values as input. + * List of different enum values:- + * - EXPAND_ALL: Expand all the accordion initially in data panel v2. + * - COLLAPSE_ALL: Collapse all the accordions initially in data panel v2. + * - EXPAND_FIRST: Expand the first accordion and collapse the rest. + * + * Supported embed types: `SearchEmbed` + * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl + * @default DataPanelCustomColumnGroupsAccordionState.EXPAND_ALL + * @example + * ```js + * const embed = new SearchEmbed('#tsEmbed', { + * ... // other embed view config + * dataPanelCustomGroupsAccordionInitialState: + * DataPanelCustomColumnGroupsAccordionState.EXPAND_ALL, + * }); + * ``` + */ + dataPanelCustomGroupsAccordionInitialState?: DataPanelCustomColumnGroupsAccordionState; + /** + * Flag to remove focus from search bar initially when user + * lands on search embed page. + * + * Supported embed types: `SearchEmbed` + * @version SDK: 1.32.0 | ThoughtSpot: 10.3.0.cl + * @default true + * @example + * ```js + * const embed = new SearchEmbed('#tsEmbed', { + * ... // other embed view config + * focusSearchBarOnRender: false, + * }); + * ``` + */ + focusSearchBarOnRender?: boolean; + /** + * Enable or disable Muze chart phase 1 GA + * + * Supported embed types: `SearchEmbed` + * @version SDK: 1.49.0 | ThoughtSpot Cloud: 26.6.0.cl + * @default false + * @example + * ```js + * const embed = new SearchEmbed('#tsEmbed', { + * ... // other embed view config + * newChartsLibrary: true, + * }) + * ``` + */ + newChartsLibrary?: boolean; + /** + * Visual overrides to customize the chart or table properties. + * @version SDK: 1.49.0 | ThoughtSpot: 26.6.0.cl + */ + visualOverrides?: VisualizationOverrides; +} +export declare const HiddenActionItemByDefaultForSearchEmbed: Action[]; +export interface SearchAppInitData extends DefaultAppInitData { + searchOptions?: SearchOptions; + embedParams?: { + visualOverridesParams?: VisualizationOverrides | null; + }; +} +/** + * Embed ThoughtSpot search + * @group Embed components + */ +export declare class SearchEmbed extends TsEmbed { + /** + * The view configuration for the embedded ThoughtSpot search. + */ + protected viewConfig: SearchViewConfig; + constructor(domSelector: DOMSelector, viewConfig: SearchViewConfig); + /** + * Get the state of the data sources panel that the embedded + * ThoughtSpot search will be initialized with. + */ + private getDataSourceMode; + protected getSearchInitData(): { + searchOptions?: { + searchTokenString: string; + }; + }; + protected getAppInitData(): Promise; + protected getEmbedParamsObject(): Record; + protected getEmbedParams(): string; + /** + * Construct the URL of the embedded ThoughtSpot search to be + * loaded in the iframe + * @param answerId The GUID of a saved answer + * @param dataSources A list of data source GUIDs + */ + getIFrameSrc(): string; + /** + * Render the embedded ThoughtSpot search + */ + render(): Promise; +} +//# sourceMappingURL=search.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/search.d.ts.map b/dist/src/embed/search.d.ts.map new file mode 100644 index 00000000..d705fc00 --- /dev/null +++ b/dist/src/embed/search.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["search.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAEH,WAAW,EAEX,MAAM,EACN,+BAA+B,EAC/B,kBAAkB,EAClB,cAAc,EACd,sBAAsB,EACzB,MAAM,UAAU,CAAC;AAOlB,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAOrC;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC1B;;OAEG;IACH,iBAAiB,EAAE,MAAM,CAAC;IAC1B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;;GAGG;AACH,oBAAY,yCAAyC;IACjD;;OAEG;IACH,UAAU,eAAe;IACzB;;OAEG;IACH,YAAY,iBAAiB;IAC7B;;OAEG;IACH,YAAY,iBAAiB;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,gBACb,SAAQ,+BAA+B,EAAE,IAAI,CAAC,cAAc,EAAE,eAAe,CAAC;IAC9E;;;;;;;;;;;;;OAaG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;;;;;;;;;;OAaG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;;;OAYG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;;;;;;;;;OAcG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;;;;;;;;OAYG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;;;;;;;OAcG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB;;;;;;;;;;;;OAYG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;;;;;;;;;;;;;;;;OAgBG;IACH,+BAA+B,CAAC,EAAE,OAAO,CAAC;IAC1C;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;;;;;;OAaG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC;;;;;OAKG;IACH,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,0CAA0C,CAAC,EAAE,yCAAyC,CAAC;IACvF;;;;;;;;;;;;;;OAcG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;;;OAaG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B;;;OAGG;IACH,eAAe,CAAC,EAAE,sBAAsB,CAAC;CAC5C;AAED,eAAO,MAAM,uCAAuC,UAMnD,CAAC;AAEF,MAAM,WAAW,iBAAkB,SAAQ,kBAAkB;IACzD,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,WAAW,CAAC,EAAE;QACV,qBAAqB,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAAC;KACzD,CAAC;CACL;AAED;;;GAGG;AACH,qBAAa,WAAY,SAAQ,OAAO;IACpC;;OAEG;IACH,SAAS,CAAC,UAAU,EAAE,gBAAgB,CAAC;gBAE3B,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,gBAAgB;IAUlE;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAazB,SAAS,CAAC,iBAAiB;;;;;cAUX,cAAc,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAiB5D,SAAS,CAAC,oBAAoB;IAmG9B,SAAS,CAAC,cAAc;IAwBxB;;;;;OAKG;IACI,YAAY,IAAI,MAAM;IAQ7B;;OAEG;IACU,MAAM,IAAI,OAAO,CAAC,WAAW,CAAC;CAkB9C"} \ No newline at end of file diff --git a/dist/src/embed/search.spec.d.ts b/dist/src/embed/search.spec.d.ts new file mode 100644 index 00000000..4e2b227e --- /dev/null +++ b/dist/src/embed/search.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=search.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/search.spec.d.ts.map b/dist/src/embed/search.spec.d.ts.map new file mode 100644 index 00000000..4b4d5656 --- /dev/null +++ b/dist/src/embed/search.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"search.spec.d.ts","sourceRoot":"","sources":["search.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/embed/spotter-utils.d.ts b/dist/src/embed/spotter-utils.d.ts new file mode 100644 index 00000000..2c734282 --- /dev/null +++ b/dist/src/embed/spotter-utils.d.ts @@ -0,0 +1,23 @@ +import { DefaultAppInitData } from '../types'; +import type { SpotterSidebarViewConfig } from './conversation'; +import type { VisualizationOverrides } from '../types'; +/** + * Resolves enablePastConversationsSidebar with + * spotterSidebarConfig taking precedence over the + * standalone flag. + */ +export declare const resolveEnablePastConversationsSidebar: (params: { + spotterSidebarConfigValue?: boolean; + standaloneValue?: boolean; +}) => boolean | undefined; +export declare function buildSpotterSidebarAppInitData(defaultAppInitData: T, viewConfig: { + spotterSidebarConfig?: SpotterSidebarViewConfig; + enablePastConversationsSidebar?: boolean; + visualOverrides?: VisualizationOverrides; +}, handleError: (err: any) => void): T & { + embedParams?: { + spotterSidebarConfig?: SpotterSidebarViewConfig; + visualOverridesParams?: VisualizationOverrides | null; + }; +}; +//# sourceMappingURL=spotter-utils.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/spotter-utils.d.ts.map b/dist/src/embed/spotter-utils.d.ts.map new file mode 100644 index 00000000..4e87fb99 --- /dev/null +++ b/dist/src/embed/spotter-utils.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"spotter-utils.d.ts","sourceRoot":"","sources":["spotter-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAsC,MAAM,UAAU,CAAC;AAGlF,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAEvD;;;;GAIG;AACH,eAAO,MAAM,qCAAqC,WAAY;IAC1D,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,eAAe,CAAC,EAAE,OAAO,CAAC;CAC7B,KAAG,OAAO,GAAG,SAIb,CAAC;AAEF,wBAAgB,8BAA8B,CAAC,CAAC,SAAS,kBAAkB,EACvE,kBAAkB,EAAE,CAAC,EACrB,UAAU,EAAE;IACR,oBAAoB,CAAC,EAAE,wBAAwB,CAAC;IAChD,8BAA8B,CAAC,EAAE,OAAO,CAAC;IACzC,eAAe,CAAC,EAAE,sBAAsB,CAAC;CAC5C,EACD,WAAW,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,GAChC,CAAC,GAAG;IACH,WAAW,CAAC,EAAE;QACV,oBAAoB,CAAC,EAAE,wBAAwB,CAAC;QAChD,qBAAqB,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAAC;KACzD,CAAC;CACL,CA+CA"} \ No newline at end of file diff --git a/dist/src/embed/spotter-utils.spec.d.ts b/dist/src/embed/spotter-utils.spec.d.ts new file mode 100644 index 00000000..5cc09af8 --- /dev/null +++ b/dist/src/embed/spotter-utils.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=spotter-utils.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/spotter-utils.spec.d.ts.map b/dist/src/embed/spotter-utils.spec.d.ts.map new file mode 100644 index 00000000..faadbcc5 --- /dev/null +++ b/dist/src/embed/spotter-utils.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"spotter-utils.spec.d.ts","sourceRoot":"","sources":["spotter-utils.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/embed/spotter-viz-utils.d.ts b/dist/src/embed/spotter-viz-utils.d.ts new file mode 100644 index 00000000..84d33e63 --- /dev/null +++ b/dist/src/embed/spotter-viz-utils.d.ts @@ -0,0 +1,85 @@ +import { DefaultAppInitData } from '../types'; +/** + * Defines starter prompts displayed in the SpotterViz interface. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @group Embed components + */ +export interface SpotterVizStarterPrompt { + /** Identifier for the prompt. */ + id: string; + /** Short label shown to the user as a clickable suggestion. */ + displayText: string; + /** Full prompt text sent to Spotter when the user clicks the suggestion. */ + fullPrompt: string; +} +/** + * Configuration for the SpotterViz interface shown on the Liveboard. + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @group Embed components + * @example + * ```js + * const embed = new AppEmbed('#embed-container', { + * ... // other options, + * spotterViz: { + * brandName: 'MyBrand', + * brandHeadline: 'Hi, there! I\'m', + * description: 'Ask questions about your data', + * inputChatPlaceholder: 'Ask a question...', + * hideStarterPrompts: false, + * customStarterPrompts: [ + * { id: '1', displayText: 'Top products', fullPrompt: 'What are the top products by revenue?' } + * ], + * }, + * }) + * ``` + */ +export interface SpotterVizConfig { + /** + * Rename the default "SpotterViz" label shown in the SpotterViz interface with a custom brand name. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @default '' + */ + brandName?: string; + /** + * Custom headline text shown before the brand name in the SpotterViz interface. + * Replaces the default greeting prefix (e.g. "Hi, there! I'm"). + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @default '' + */ + brandHeadline?: string; + /** + * Hides the starter prompts section entirely in the SpotterViz interface. + * When set to `true`, the starter prompts are not displayed. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @default false + */ + hideStarterPrompts?: boolean; + /** + * Overrides the starter prompts with a custom list. + * Each entry must match the {@link SpotterVizStarterPrompt} shape. + * Has no effect when `hideStarterPrompts` is `true`. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + */ + customStarterPrompts?: SpotterVizStarterPrompt[]; + /** + * Custom description text shown in the SpotterViz interface. + * Replaces the default SpotterViz description. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + */ + description?: string; + /** + * Custom placeholder text for the chat input in the SpotterViz interface. + * Replaces the default chat input placeholder text. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + */ + inputChatPlaceholder?: string; +} +export declare function buildSpotterVizAppInitData(initData: T, viewConfig: { + spotterViz?: SpotterVizConfig; +}): T & { + embedParams?: { + spotterVizConfig?: SpotterVizConfig; + }; +}; +//# sourceMappingURL=spotter-viz-utils.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/spotter-viz-utils.d.ts.map b/dist/src/embed/spotter-viz-utils.d.ts.map new file mode 100644 index 00000000..4ae98fe6 --- /dev/null +++ b/dist/src/embed/spotter-viz-utils.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"spotter-viz-utils.d.ts","sourceRoot":"","sources":["spotter-viz-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAE9C;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACpC,iCAAiC;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,UAAU,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,gBAAgB;IAC7B;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,uBAAuB,EAAE,CAAC;IACjD;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,wBAAgB,0BAA0B,CAAC,CAAC,SAAS,kBAAkB,EACnE,QAAQ,EAAE,CAAC,EACX,UAAU,EAAE;IAAE,UAAU,CAAC,EAAE,gBAAgB,CAAA;CAAE,GAC9C,CAAC,GAAG;IAAE,WAAW,CAAC,EAAE;QAAE,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;KAAE,CAAA;CAAE,CAU/D"} \ No newline at end of file diff --git a/dist/src/embed/spotter-viz-utils.spec.d.ts b/dist/src/embed/spotter-viz-utils.spec.d.ts new file mode 100644 index 00000000..9f3e1f00 --- /dev/null +++ b/dist/src/embed/spotter-viz-utils.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=spotter-viz-utils.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/spotter-viz-utils.spec.d.ts.map b/dist/src/embed/spotter-viz-utils.spec.d.ts.map new file mode 100644 index 00000000..12e26b56 --- /dev/null +++ b/dist/src/embed/spotter-viz-utils.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"spotter-viz-utils.spec.d.ts","sourceRoot":"","sources":["spotter-viz-utils.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/embed/ts-embed-trigger.spec.d.ts b/dist/src/embed/ts-embed-trigger.spec.d.ts new file mode 100644 index 00000000..fb91b92e --- /dev/null +++ b/dist/src/embed/ts-embed-trigger.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=ts-embed-trigger.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/ts-embed-trigger.spec.d.ts.map b/dist/src/embed/ts-embed-trigger.spec.d.ts.map new file mode 100644 index 00000000..fdac37e1 --- /dev/null +++ b/dist/src/embed/ts-embed-trigger.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"ts-embed-trigger.spec.d.ts","sourceRoot":"","sources":["ts-embed-trigger.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/embed/ts-embed.d.ts b/dist/src/embed/ts-embed.d.ts new file mode 100644 index 00000000..d81905b9 --- /dev/null +++ b/dist/src/embed/ts-embed.d.ts @@ -0,0 +1,570 @@ +/** + * Copyright (c) 2022 + * + * Base classes + * @summary Base classes + * @author Ayon Ghosh + */ +import { TriggerPayload, TriggerResponse, UIPassthroughArrayResponse, UIPassthroughEvent, UIPassthroughRequest } from './hostEventClient/contracts'; +import { AnswerService } from '../utils/graphql/answerService/answerService'; +import { DOMSelector, HostEvent, EmbedEvent, MessageCallback, Action, EmbedConfig, MessageOptions, DefaultAppInitData, AllEmbedViewConfig as ViewConfig, EmbedErrorDetailsEvent, ContextType, ContextObject } from '../types'; +import { HostEventClient } from './hostEventClient/host-event-client'; +/** + * Global prefix for all ThoughtSpot postHash Params. + */ +export declare const THOUGHTSPOT_PARAM_PREFIX = "ts-"; +/** + * Base class for embedding v2 experience + * Note: the v2 version of ThoughtSpot Blink is built on the new stack: + * React+GraphQL + */ +export declare class TsEmbed { + /** + * The DOM node which was inserted by the SDK to either + * render the iframe or display an error message. + * This is useful for removing the DOM node when the + * embed instance is destroyed. + */ + protected insertedDomEl: Node; + /** + * The DOM node where the ThoughtSpot app is to be embedded. + */ + protected hostElement: HTMLElement; + /** + * The key to store the embed instance in the DOM node + */ + protected embedNodeKey: string; + protected isAppInitialized: boolean; + /** + * A reference to the iframe within which the ThoughtSpot app + * will be rendered. + */ + protected iFrame: HTMLIFrameElement; + /** + * Setter for the iframe element + * @param {HTMLIFrameElement} iFrame HTMLIFrameElement + */ + protected setIframeElement(iFrame: HTMLIFrameElement): void; + protected viewConfig: ViewConfig & { + visibleTabs?: string[]; + hiddenTabs?: string[]; + showAlerts?: boolean; + }; + protected embedConfig: EmbedConfig; + /** + * The ThoughtSpot hostname or IP address + */ + protected thoughtSpotHost: string; + protected thoughtSpotV2Base: string; + /** + * A map of event handlers for particular message types triggered + * by the embedded app; multiple event handlers can be registered + * against a particular message type. + */ + private eventHandlerMap; + /** + * A flag that is set to true post render. + */ + protected isRendered: boolean; + /** + * A flag to mark if an error has occurred. + */ + private isError; + /** + * A flag that is set to true post preRender. + */ + private isPreRendered; + /** + * Should we encode URL Query Params using base64 encoding which ThoughtSpot + * will generate for embedding. This provides additional security to + * ThoughtSpot clusters against Cross site scripting attacks. + * @default false + */ + private shouldEncodeUrlQueryParams; + private defaultHiddenActions; + private resizeObserver; + protected hostEventClient: HostEventClient; + protected isReadyForRenderPromise: Promise; + protected shouldWaitForRenderPromise: boolean; + /** + * Handler for fullscreen change events + */ + private fullscreenChangeHandler; + constructor(domSelector: DOMSelector, viewConfig?: ViewConfig); + /** + * Throws error encountered during initialization. + */ + private throwInitError; + /** + * Handles errors within the SDK + * @param error The error message or object + * @param errorDetails The error details + */ + protected handleError(errorDetails: EmbedErrorDetailsEvent): void; + /** + * Extracts the type field from the event payload + * @param event The window message event + */ + private getEventType; + /** + * Extracts the port field from the event payload + * @param event The window message event + * @returns + */ + private getEventPort; + /** + * Checks if preauth cache is enabled + * from the view config and embed config + * @returns boolean + */ + private isPreAuthCacheEnabled; + /** + * Checks if current embed is FullAppEmbed with visible primary navbar + * @returns boolean + */ + private isFullAppEmbedWithVisiblePrimaryNavbar; + /** + * fix for ts7.sep.cl + * will be removed for ts7.oct.cl + * @param event + * @param eventType + * @hidden + */ + private formatEventData; + private subscribedListeners; + /** + * Subscribe to network events (online/offline) that should + * work regardless of auth status + */ + private subscribeToNetworkEvents; + private handleApiInterceptEvent; + private messageEventListener; + /** + * Subscribe to message events that depend on successful iframe setup + */ + private subscribeToMessageEvents; + /** + * Adds event listeners for both network and message events. + * This maintains backward compatibility with the existing method. + * Adds a global event listener to window for "message" events. + * ThoughtSpot detects if a particular event is targeted to this + * embed instance through an identifier contained in the payload, + * and executes the registered callbacks accordingly. + */ + private subscribeToEvents; + private unsubscribeToNetworkEvents; + private unsubscribeToMessageEvents; + private unsubscribeToEvents; + protected getAuthTokenForCookielessInit(): Promise; + protected getDefaultAppInitData(): Promise; + protected getAppInitData(): Promise; + /** + * Send Custom style as part of payload of APP_INIT + * @param _ + * @param responder + */ + private appInitCb; + /** + * Helper method to refresh/update auth token for TrustedAuthTokenCookieless auth type + * @param responder - Function to send response back + * @param eventType - The embed event type to send + * @param forceRefresh - Whether to force refresh the token + * @returns Promise that resolves if token was refreshed, rejects otherwise + */ + private refreshAuthTokenForCookieless; + private handleAuthFailure; + /** + * Refresh the auth token if the autoLogin is true and the authType is TrustedAuthTokenCookieless + * @param _ + * @param responder + */ + private tokenRefresh; + /** + * Sends updated auth token to the iFrame to avoid user logout + * @param _ + * @param responder + */ + private updateAuthToken; + /** + * Auto Login and send updated authToken to the iFrame to avoid user session logout + * @param _ + * @param responder + */ + private idleSessionTimeout; + /** + * Register APP_INIT event and sendback init payload + */ + private registerAppInit; + /** + * Constructs the base URL string to load the ThoughtSpot app. + * @param query + */ + protected getEmbedBasePath(query: string): string; + protected getUpdateEmbedParamsObject(): Promise>; + /** + * Common query params set for all the embed modes. + * @param queryParams + * @returns queryParams + */ + protected getBaseQueryParams(queryParams?: Record): Record; + /** + * Constructs the base URL string to load v1 of the ThoughtSpot app. + * This is used for embedding Liveboards, visualizations, and full application. + * @param queryString The query string to append to the URL. + * @param isAppEmbed A Boolean parameter to specify if you are embedding + * the full application. + */ + protected getV1EmbedBasePath(queryString: string): string; + protected getEmbedParams(): string; + protected getEmbedParamsObject(): Record; + protected getRootIframeSrc(): string; + protected createIframeEl(frameSrc: string): HTMLIFrameElement; + /** + * Returns true if this embed instance is configured for pre-rendering. + */ + protected isPreRenderEmbed(): boolean; + protected handleInsertionIntoDOM(child: string | Node): void; + /** + * Renders the embedded ThoughtSpot app in an iframe and sets up + * event listeners. + * @param url - The URL of the embedded ThoughtSpot app. + */ + protected renderIFrame(url: string): Promise; + protected createPreRenderWrapper(): HTMLDivElement; + protected preRenderWrapper: HTMLElement; + protected preRenderChild: HTMLElement; + /** + * Checks for an existing pre-rendered component and connects to it. + * + * If a matching pre-rendered component is found in the DOM, this method + * sets the internal properties of the embed object to reference it. + * + * @returns True if a connection was successfully established, false otherwise. + */ + protected connectPreRendered(): boolean; + protected isPreRenderConnected(): boolean; + protected createPreRenderChild(child: string | Node): HTMLElement; + /** + * Creates the in-flow placeholder div inserted into the host element when + * showPreRender() is called. The wrapper observes this element to stay + * aligned with the host layout. + */ + private createPreRenderPlaceholder; + protected insertIntoDOMForPreRender(child: string | Node): void; + private showPreRenderByDefault; + protected insertIntoDOM(child: string | Node): void; + /** + * Sets the height of the iframe + * @param height The height in pixels + */ + protected setIFrameHeight(height: number | string): void; + /** + * We can process the customer given payload before sending it to the embed port + * Embed event handler -> responder -> createEmbedEventResponder -> send response + * @param eventPort The event port for a specific MessageChannel + * @param eventType The event type + * @returns + */ + protected createEmbedEventResponder: (eventPort: MessagePort | void, eventType: EmbedEvent) => (payload: any) => void; + private shouldSkipEvent; + /** + * Executes all registered event handlers for a particular event type + * @param eventType The event type + * @param data The payload invoked with the event handler + * @param eventPort The event Port for a specific MessageChannel + */ + protected executeCallbacks(eventType: EmbedEvent, data: any, eventPort?: MessagePort | void): void; + /** + * Returns the ThoughtSpot hostname or IP address. + */ + protected getThoughtSpotHost(): string; + /** + * Gets the v1 event type (if applicable) for the EmbedEvent type + * @param eventType The v2 event type + * @returns The corresponding v1 event type if one exists + * or else the v2 event type itself + */ + protected getCompatibleEventType(eventType: EmbedEvent): EmbedEvent; + /** + * Calculates the iframe center for the current visible viewPort + * of iframe using Scroll position of Host App, offsetTop for iframe + * in Host app. ViewPort height of the tab. + * @returns iframe Center in visible viewport, + * Iframe height, + * View port height. + */ + protected getIframeCenter(): { + iframeCenter: number; + iframeScrolled: number; + iframeHeight: number; + viewPortHeight: number; + iframeVisibleViewPort: number; + }; + /** + * Registers an event listener to trigger an alert when the ThoughtSpot app + * sends an event of a particular message type to the host application. + * @param messageType The message type + * @param callback A callback as a function + * @param options The message options + * @param isSelf + * @param isRegisteredBySDK + * @example + * ```js + * tsEmbed.on(EmbedEvent.Error, (data) => { + * console.error(data); + * }); + * ``` + * @example + * ```js + * tsEmbed.on(EmbedEvent.Save, (data) => { + * console.log("Answer save clicked", data); + * }, { + * start: true // This will trigger the callback on start of save + * }); + * ``` + */ + on(messageType: EmbedEvent, callback: MessageCallback, options?: MessageOptions, isRegisteredBySDK?: boolean): typeof TsEmbed.prototype; + /** + * Removes an event listener for a particular event type. + * @param messageType The message type + * @param callback The callback to remove + * @example + * ```js + * const errorHandler = (data) => { console.error(data); }; + * tsEmbed.on(EmbedEvent.Error, errorHandler); + * tsEmbed.off(EmbedEvent.Error, errorHandler); + * ``` + */ + off(messageType: EmbedEvent, callback: MessageCallback): typeof TsEmbed.prototype; + /** + * Triggers an event on specific Port registered against + * for the EmbedEvent + * @param eventType The message type + * @param data The payload to send + * @param eventPort + * @param payload + */ + private triggerEventOnPort; + /** + * @hidden + * Internal state to track if the embed container is loaded. + * This is used to trigger events after the embed container is loaded. + */ + isEmbedContainerLoaded: boolean; + /** + * @hidden + * Internal state to track the callbacks to be executed after the embed container + * is loaded. + * This is used to trigger events after the embed container is loaded. + */ + private embedContainerReadyCallbacks; + protected getPreRenderObj(): T; + private checkEmbedContainerLoaded; + private executeEmbedContainerReadyCallbacks; + /** + * Executes a callback after the embed container is loaded. + * @param callback The callback to execute + */ + protected executeAfterEmbedContainerLoaded(callback: () => void): void; + protected createEmbedContainerHandler: (source: EmbedEvent.AuthInit | EmbedEvent.EmbedListenerReady) => () => void; + /** + * Triggers an event to the embedded app + * @param {HostEvent} messageType The event type + * @param {any} data The payload to send with the message + * @param {ContextType} context Optional context type to specify the context from which the event is triggered. + * Use ContextType.Search for search answer context, ContextType.Answer for answer/explore context, + * ContextType.Liveboard for liveboard context, or ContextType.Spotter for spotter context. + * Available from SDK version 1.45.2 | ThoughtSpot: 26.3.0.cl + * @returns A promise that resolves with the response from the embedded app + * @example + * ```js + * // Trigger Pin event with context (SDK: 1.45.2+) + * import { HostEvent, ContextType } from '@thoughtspot/visual-embed-sdk'; + * embed.trigger(HostEvent.Pin, { + * vizId: "123", + * liveboardId: "456" + * }, ContextType.Search); + * ``` + * @version SDK: 1.45.2 | ThoughtSpot: 26.3.0.cl (for context parameter) + */ + trigger(messageType: HostEventT, data?: TriggerPayload, context?: ContextT): Promise>; + /** + * Triggers an event to the embedded app, skipping the UI flow. + * @param {UIPassthroughEvent} apiName - The name of the API to be triggered. + * @param {UIPassthroughRequest} parameters - The parameters to be passed to the API. + * @returns {Promise} - A promise that resolves with the response + * from the embedded app. + */ + triggerUIPassThrough(apiName: UIPassthroughEventT, parameters: UIPassthroughRequest): Promise>; + /** + * Marks the ThoughtSpot object to have been rendered + * Needs to be overridden by subclasses to do the actual + * rendering of the iframe. + * @param args + */ + render(): Promise; + getIframeSrc(): string; + protected handleRenderForPrerender(): Promise; + /** + * Context object for the embedded component. + * @returns {ContextObject} The current context object containing the page type and object ids. + * @example + * ```js + * const context = await embed.getCurrentContext(); + * console.log(context); + * + * // Example output + * { + * stack: [ + * { + * name: 'Liveboard', + * type: ContextType.Liveboard, + * objectIds: { + * liveboardId: '123', + * }, + * }, + * ], + * currentContext: { + * name: 'Liveboard', + * type: ContextType.Liveboard, + * objectIds: { + * liveboardId: '123', + * }, + * }, + * } + * ``` + * @version SDK: 1.45.2 | ThoughtSpot: 26.3.0.cl + */ + getCurrentContext(): Promise; + /** + * Generates the event name for a "Subscribed" embed event. + * + * This helper appends the "Subscribed" suffix to a given host or action event, + * allowing you to listen for subscription lifecycle events in a consistent format. + * + * @param eventName - The host or action event to generate the subscribed event name for. + * @returns The formatted event name (e.g., "Save Subscribed"). + * + * @version SDK: 1.47.2 | ThoughtSpot: 26.3.0.cl + */ + subscribedEvent(eventName: HostEvent | Action): string; + /** + * Creates the preRender shell + * @param showPreRenderByDefault - Show the preRender after render, hidden by default + */ + preRender(showPreRenderByDefault?: boolean, replaceExistingPreRender?: boolean): Promise; + /** + * Get the Post Url Params for THOUGHTSPOT from the current + * host app URL. + * THOUGHTSPOT URL params starts with a prefix "ts-" + * @version SDK: 1.14.0 | ThoughtSpot: 8.4.0.cl, 8.4.1-sw + */ + getThoughtSpotPostUrlParams(additionalParams?: { + [key: string]: string | number; + }): string; + /** + * Destroys the ThoughtSpot embed, and remove any nodes from the DOM. + * @version SDK: 1.19.1 | ThoughtSpot: * + */ + destroy(): void; + getUnderlyingFrameElement(): HTMLIFrameElement; + /** + * Prerenders a generic instance of the TS component. + * This means without the path but with the flags already applied. + * This is useful for prerendering the component in the background. + * @version SDK: 1.22.0 + * @returns + */ + prerenderGeneric(): Promise; + protected beforePrerenderVisible(): void; + /** + * Displays the pre-rendered component inside the host element. + * If the component has not been pre-rendered yet, it initiates rendering first. + * Inserts a placeholder element into the host and positions the pre-render + * wrapper to overlay it. + */ + showPreRender(): Promise; + protected getPreRenderPlaceHolderElement(): HTMLDivElement; + /** + * Synchronizes the style properties of the PreRender component with the embedding + * element. This function adjusts the position, width, and height of the PreRender + * component + * to match the dimensions and position of the embedding element. + * @throws {Error} Throws an error if the embedding element (passed as domSelector) + * is not defined or not found. + */ + syncPreRenderStyle(): void; + /** + * Hides the PreRender component if it is available. + * If the component is not preRendered, it issues a warning. + */ + hidePreRender(): void; + /** + * Retrieves unique HTML element IDs for PreRender-related elements. + * These IDs are constructed based on the provided 'preRenderId' from 'viewConfig'. + * @returns {object} An object containing the IDs for the PreRender elements. + * @property {string} wrapper - The HTML element ID for the PreRender wrapper. + * @property {string} child - The HTML element ID for the PreRender child. + */ + getPreRenderIds(): { + wrapper: string; + child: string; + placeHolder: string; + }; + /** + * Returns the answerService which can be used to make arbitrary graphql calls on top + * session. + * @param vizId [Optional] to get for a specific viz in case of a Liveboard. + * @version SDK: 1.25.0 | ThoughtSpot: 9.10.0 + */ + getAnswerService(vizId?: string): Promise; + /** + * Set up fullscreen change detection to automatically trigger ExitPresentMode + * when user exits fullscreen mode + */ + private setupFullscreenChangeHandler; + /** + * Remove fullscreen change handler + */ + private removeFullscreenChangeHandler; +} +/** + * Base class for embedding v1 experience + * Note: The v1 version of ThoughtSpot Blink works on the AngularJS stack + * which is currently under migration to v2 + * @inheritdoc + */ +export declare class V1Embed extends TsEmbed { + protected viewConfig: ViewConfig; + constructor(domSelector: DOMSelector, viewConfig: ViewConfig); + /** + * Render the app in an iframe and set up event handlers + * @param iframeSrc + */ + protected renderV1Embed(iframeSrc: string): Promise; + protected getRootIframeSrc(): string; + /** + * @inheritdoc + * @example + * ```js + * tsEmbed.on(EmbedEvent.Error, (data) => { + * console.error(data); + * }); + * ``` + * @example + * ```js + * tsEmbed.on(EmbedEvent.Save, (data) => { + * console.log("Answer save clicked", data); + * }, { + * start: true // This will trigger the callback on start of save + * }); + * ``` + */ + on(messageType: EmbedEvent, callback: MessageCallback, options?: MessageOptions): typeof TsEmbed.prototype; + /** + * Only for testing purposes. + * @hidden + */ + test__executeCallbacks: (eventType: EmbedEvent, data: any, eventPort?: void | MessagePort) => void; +} +//# sourceMappingURL=ts-embed.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/ts-embed.d.ts.map b/dist/src/embed/ts-embed.d.ts.map new file mode 100644 index 00000000..1c08ca99 --- /dev/null +++ b/dist/src/embed/ts-embed.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"ts-embed.d.ts","sourceRoot":"","sources":["ts-embed.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,EACH,cAAc,EACd,eAAe,EACf,0BAA0B,EAC1B,kBAAkB,EAClB,oBAAoB,EACvB,MAAM,6BAA6B,CAAC;AAGrC,OAAO,EAAE,aAAa,EAAE,MAAM,8CAA8C,CAAC;AA2B7E,OAAO,EAEH,WAAW,EACX,SAAS,EACT,UAAU,EACV,eAAe,EACf,MAAM,EAEN,WAAW,EACX,cAAc,EAGd,kBAAkB,EAClB,kBAAkB,IAAI,UAAU,EAChC,sBAAsB,EAItB,WAAW,EACX,aAAa,EAChB,MAAM,UAAU,CAAC;AAclB,OAAO,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AAGtE;;GAEG;AACH,eAAO,MAAM,wBAAwB,QAAQ,CAAC;AAY9C;;;;GAIG;AACH,qBAAa,OAAO;IAChB;;;;;OAKG;IACH,SAAS,CAAC,aAAa,EAAE,IAAI,CAAC;IAE9B;;OAEG;IACH,SAAS,CAAC,WAAW,EAAE,WAAW,CAAC;IAEnC;;OAEG;IACH,SAAS,CAAC,YAAY,SAAe;IAErC,SAAS,CAAC,gBAAgB,UAAS;IAEnC;;;OAGG;IACH,SAAS,CAAC,MAAM,EAAE,iBAAiB,CAAC;IAEpC;;;OAGG;IACH,SAAS,CAAC,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI;IAK3D,SAAS,CAAC,UAAU,EAAE,UAAU,GAAG;QAAE,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAE3G,SAAS,CAAC,WAAW,EAAE,WAAW,CAAC;IAEnC;;OAEG;IACH,SAAS,CAAC,eAAe,EAAE,MAAM,CAAC;IAKlC,SAAS,CAAC,iBAAiB,EAAE,MAAM,CAAC;IAEpC;;;;OAIG;IACH,OAAO,CAAC,eAAe,CAAoC;IAE3D;;OAEG;IACH,SAAS,CAAC,UAAU,EAAE,OAAO,CAAC;IAE9B;;OAEG;IACH,OAAO,CAAC,OAAO,CAAU;IAEzB;;OAEG;IACH,OAAO,CAAC,aAAa,CAAU;IAE/B;;;;;OAKG;IACH,OAAO,CAAC,0BAA0B,CAAS;IAE3C,OAAO,CAAC,oBAAoB,CAAwB;IAEpD,OAAO,CAAC,cAAc,CAAiB;IAEvC,SAAS,CAAC,eAAe,EAAE,eAAe,CAAC;IAE3C,SAAS,CAAC,uBAAuB,gBAAC;IAClC,SAAS,CAAC,0BAA0B,EAAE,OAAO,CAAC;IAE9C;;OAEG;IACH,OAAO,CAAC,uBAAuB,CAA6B;gBAEhD,WAAW,EAAE,WAAW,EAAE,UAAU,CAAC,EAAE,UAAU;IAwC7D;;OAEG;IACH,OAAO,CAAC,cAAc;IAStB;;;;OAIG;IACH,SAAS,CAAC,WAAW,CAAC,YAAY,EAAE,sBAAsB;IAO1D;;;OAGG;IACH,OAAO,CAAC,YAAY;IAKpB;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAOpB;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAiB7B;;;OAGG;IACH,OAAO,CAAC,sCAAsC;IAW9C;;;;;;OAMG;IACH,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,mBAAmB,CAA2B;IAEtD;;;OAGG;IACH,OAAO,CAAC,wBAAwB;IAwBhC,OAAO,CAAC,uBAAuB;IAW/B,OAAO,CAAC,oBAAoB,CAuB1B;IACF;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAShC;;;;;;;OAOG;IACH,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,0BAA0B;IAWlC,OAAO,CAAC,0BAA0B;IAOlC,OAAO,CAAC,mBAAmB;cAMX,6BAA6B;cAc7B,qBAAqB,IAAI,OAAO,CAAC,kBAAkB,CAAC;cAyCpD,cAAc;IAI9B;;;;OAIG;IACH,OAAO,CAAC,SAAS,CAWf;IAEF;;;;;;OAMG;YACW,6BAA6B;IAiB3C,OAAO,CAAC,iBAAiB,CAGxB;IAED;;;;OAIG;IACH,OAAO,CAAC,YAAY,CAMnB;IAED;;;;OAIG;IACH,OAAO,CAAC,eAAe,CAgBrB;IAEF;;;;OAIG;IACH,OAAO,CAAC,kBAAkB,CAgBxB;IAEF;;OAEG;IACH,OAAO,CAAC,eAAe,CAWrB;IAEF;;;OAGG;IACH,SAAS,CAAC,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;cAcjC,0BAA0B;IAQ1C;;;;OAIG;IACH,SAAS,CAAC,kBAAkB,CAAC,WAAW,GAAE,MAAM,CAAC,GAAG,EAAE,GAAG,CAAM;IA+K/D;;;;;;OAMG;IACH,SAAS,CAAC,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IASzD,SAAS,CAAC,cAAc;IAKxB,SAAS,CAAC,oBAAoB;IAK9B,SAAS,CAAC,gBAAgB;IAK1B,SAAS,CAAC,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,iBAAiB;IAqC7D;;OAEG;IACH,SAAS,CAAC,gBAAgB;IAG1B,SAAS,CAAC,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAc5D;;;;OAIG;cACa,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IA2FvD,SAAS,CAAC,sBAAsB,IAAI,cAAc;IAmBlD,SAAS,CAAC,gBAAgB,EAAE,WAAW,CAAC;IAExC,SAAS,CAAC,cAAc,EAAE,WAAW,CAAC;IAEtC;;;;;;;OAOG;IACH,SAAS,CAAC,kBAAkB,IAAI,OAAO;IAkBvC,SAAS,CAAC,oBAAoB,IAAI,OAAO;IAMzC,SAAS,CAAC,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW;IAuBjE;;;;OAIG;IACH,OAAO,CAAC,0BAA0B;IAalC,SAAS,CAAC,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IA0B/D,OAAO,CAAC,sBAAsB,CAAS;IAEvC,SAAS,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAwBnD;;;OAGG;IACH,SAAS,CAAC,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAYxD;;;;;;OAMG;IACH,SAAS,CAAC,yBAAyB,cAAe,WAAW,GAAG,IAAI,aAAa,UAAU,eAWtE,GAAG,UAIvB;IAED,OAAO,CAAC,eAAe;IAYvB;;;;;OAKG;IACH,SAAS,CAAC,gBAAgB,CACtB,SAAS,EAAE,UAAU,EACrB,IAAI,EAAE,GAAG,EACT,SAAS,CAAC,EAAE,WAAW,GAAG,IAAI,GAC/B,IAAI;IAqBP;;OAEG;IACH,SAAS,CAAC,kBAAkB,IAAI,MAAM;IAItC;;;;;OAKG;IACH,SAAS,CAAC,sBAAsB,CAAC,SAAS,EAAE,UAAU,GAAG,UAAU;IAInE;;;;;;;OAOG;IACH,SAAS,CAAC,eAAe;;;;;;;IA2BzB;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACI,EAAE,CACL,WAAW,EAAE,UAAU,EACvB,QAAQ,EAAE,eAAe,EACzB,OAAO,GAAE,cAAiC,EAC1C,iBAAiB,UAAQ,GAC1B,OAAO,OAAO,CAAC,SAAS;IAa3B;;;;;;;;;;OAUG;IACI,GAAG,CAAC,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,eAAe,GAAG,OAAO,OAAO,CAAC,SAAS;IASxF;;;;;;;OAOG;IACH,OAAO,CAAC,kBAAkB;IAgB1B;;;;MAIE;IACK,sBAAsB,UAAS;IAEtC;;;;;OAKG;IACH,OAAO,CAAC,4BAA4B,CAAyB;IAE7D,SAAS,CAAC,eAAe,CAAC,CAAC,SAAS,OAAO,KAAK,CAAC;IAQjD,OAAO,CAAC,yBAAyB;IAUjC,OAAO,CAAC,mCAAmC;IAQ3C;;;OAGG;IACH,SAAS,CAAC,gCAAgC,CAAC,QAAQ,EAAE,MAAM,IAAI;IAS/D,SAAS,CAAC,2BAA2B,WAAY,WAAW,QAAQ,GAAG,WAAW,kBAAkB,gBAenG;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACU,OAAO,CAAC,UAAU,SAAS,SAAS,EAAE,QAAQ,EAAE,QAAQ,SAAS,WAAW,EACrF,WAAW,EAAE,UAAU,EACvB,IAAI,GAAE,cAAc,CAAC,QAAQ,EAAE,UAAU,CAAa,EACtD,OAAO,CAAC,EAAE,QAAQ,GACnB,OAAO,CAAC,eAAe,CAAC,QAAQ,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;IAkD3D;;;;;;OAMG;IACU,oBAAoB,CAAC,mBAAmB,SAAS,kBAAkB,EAC5E,OAAO,EAAE,mBAAmB,EAC5B,UAAU,EAAE,oBAAoB,CAAC,mBAAmB,CAAC,GACtD,OAAO,CAAC,0BAA0B,CAAC,mBAAmB,CAAC,CAAC;IAK3D;;;;;OAKG;IACU,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAWhC,YAAY,IAAI,MAAM;IAI7B,SAAS,CAAC,wBAAwB;IAIlC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACU,iBAAiB,IAAI,OAAO,CAAC,aAAa,CAAC;IASxD;;;;;;;;;;OAUG;IACI,eAAe,CAAC,SAAS,EAAE,SAAS,GAAG,MAAM,GAAG,MAAM;IAI7D;;;OAGG;IAEU,SAAS,CAAC,sBAAsB,UAAQ,EAAE,wBAAwB,UAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IAoB1G;;;;;OAKG;IACI,2BAA2B,CAC9B,gBAAgB,GAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;KAAO,GAC1D,MAAM;IAwBT;;;OAGG;IACI,OAAO,IAAI,IAAI;IA+Bf,yBAAyB,IAAI,iBAAiB;IAIrD;;;;;;OAMG;IACU,gBAAgB,IAAI,OAAO,CAAC,GAAG,CAAC;IAY7C,SAAS,CAAC,sBAAsB,IAAI,IAAI;IAsBxC;;;;;OAKG;IACU,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC;IA+D9C,SAAS,CAAC,8BAA8B;IAIxC;;;;;;;OAOG;IACI,kBAAkB,IAAI,IAAI;IAgBjC;;;OAGG;IACI,aAAa,IAAI,IAAI;IA8B5B;;;;;;OAMG;IACI,eAAe;;;;;IAQtB;;;;;OAKG;IACU,gBAAgB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAKrE;;;OAGG;IACH,OAAO,CAAC,4BAA4B;IA6BpC;;OAEG;IACH,OAAO,CAAC,6BAA6B;CAMxC;AAED;;;;;GAKG;AACH,qBAAa,OAAQ,SAAQ,OAAO;IAChC,SAAS,CAAC,UAAU,EAAE,UAAU,CAAC;gBAErB,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU;IAS5D;;;OAGG;IACH,SAAS,CAAC,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAIxD,SAAS,CAAC,gBAAgB,IAAI,MAAM;IAqBpC;;;;;;;;;;;;;;;;OAgBG;IACI,EAAE,CACL,WAAW,EAAE,UAAU,EACvB,QAAQ,EAAE,eAAe,EACzB,OAAO,GAAE,cAAiC,GAC3C,OAAO,OAAO,CAAC,SAAS;IAK3B;;;OAGG;IAEI,sBAAsB,6EAAyB;CACzD"} \ No newline at end of file diff --git a/dist/src/embed/ts-embed.spec.d.ts b/dist/src/embed/ts-embed.spec.d.ts new file mode 100644 index 00000000..7917e434 --- /dev/null +++ b/dist/src/embed/ts-embed.spec.d.ts @@ -0,0 +1,3 @@ +export declare const defaultParamsWithoutHiddenActions: string; +export declare const defaultParams: string; +//# sourceMappingURL=ts-embed.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/embed/ts-embed.spec.d.ts.map b/dist/src/embed/ts-embed.spec.d.ts.map new file mode 100644 index 00000000..4b76e97f --- /dev/null +++ b/dist/src/embed/ts-embed.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"ts-embed.spec.d.ts","sourceRoot":"","sources":["ts-embed.spec.ts"],"names":[],"mappings":"AAqFA,eAAO,MAAM,iCAAiC,QAAgJ,CAAC;AAC/L,eAAO,MAAM,aAAa,QAAmF,CAAC"} \ No newline at end of file diff --git a/dist/src/errors.d.ts b/dist/src/errors.d.ts new file mode 100644 index 00000000..12b6584d --- /dev/null +++ b/dist/src/errors.d.ts @@ -0,0 +1,45 @@ +export declare const ERROR_MESSAGE: { + INVALID_THOUGHTSPOT_HOST: string; + SPOTTER_EMBED_WORKSHEED_ID_NOT_FOUND: string; + LIVEBOARD_VIZ_ID_VALIDATION: string; + TRIGGER_TIMED_OUT: string; + SEARCHEMBED_BETA_WRANING_MESSAGE: string; + THIRD_PARTY_COOKIE_BLOCKED_ALERT: string; + DUPLICATE_TOKEN_ERR: string; + SDK_NOT_INITIALIZED: string; + SESSION_INFO_FAILED: string; + INVALID_TOKEN_ERROR: string; + INVALID_TOKEN_TYPE_ERROR: string; + MIXPANEL_TOKEN_NOT_FOUND: string; + PRERENDER_ID_MISSING: string; + SYNC_STYLE_CALLED_BEFORE_RENDER: string; + CSP_VIOLATION_ALERT: string; + CSP_FRAME_HOST_VIOLATION_LOG_MESSAGE: string; + MISSING_REPORTING_OBSERVER: string; + RENDER_CALLED_BEFORE_INIT: string; + SPOTTER_AGENT_NOT_INITIALIZED: string; + OFFLINE_WARNING: string; + INIT_SDK_REQUIRED: string; + CONFLICTING_ACTIONS_CONFIG: string; + CONFLICTING_TABS_CONFIG: string; + RENDER_BEFORE_EVENTS_REQUIRED: string; + HOST_EVENT_TYPE_UNDEFINED: string; + LOGIN_FAILED: string; + ERROR_PARSING_API_INTERCEPT_BODY: string; + SSR_ENVIRONMENT_ERROR: string; + UPDATE_PARAMS_FAILED: string; + INVALID_SPOTTER_DOCUMENTATION_URL: string; + UPDATEFILTERS_INVALID_PAYLOAD: string; + DRILLDOWN_INVALID_PAYLOAD: string; +}; +export declare const CUSTOM_ACTIONS_ERROR_MESSAGE: { + INVALID_ACTION_OBJECT: string; + MISSING_REQUIRED_FIELDS: (id: string, missingFields: string[]) => string; + UNSUPPORTED_TARGET: (id: string, targetType: string) => string; + INVALID_POSITION: (position: string, targetType: string, supportedPositions: string) => string; + INVALID_METADATA_IDS: (targetType: string, invalidIds: string[], supportedIds: string) => string; + INVALID_DATA_MODEL_IDS: (targetType: string, invalidIds: string[], supportedIds: string) => string; + INVALID_FIELDS: (targetType: string, invalidFields: string[], supportedFields: string) => string; + DUPLICATE_IDS: (id: string, duplicateNames: string[], keptName: string) => string; +}; +//# sourceMappingURL=errors.d.ts.map \ No newline at end of file diff --git a/dist/src/errors.d.ts.map b/dist/src/errors.d.ts.map new file mode 100644 index 00000000..2bbcac7b --- /dev/null +++ b/dist/src/errors.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["errors.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCzB,CAAC;AAEF,eAAO,MAAM,4BAA4B;;kCAEP,MAAM,iBAAiB,MAAM,EAAE;6BACpC,MAAM,cAAc,MAAM;iCACtB,MAAM,cAAc,MAAM,sBAAsB,MAAM;uCAChD,MAAM,cAAc,MAAM,EAAE,gBAAgB,MAAM;yCAChD,MAAM,cAAc,MAAM,EAAE,gBAAgB,MAAM;iCAC1D,MAAM,iBAAiB,MAAM,EAAE,mBAAmB,MAAM;wBACjE,MAAM,kBAAkB,MAAM,EAAE,YAAY,MAAM;CACzE,CAAC"} \ No newline at end of file diff --git a/dist/src/index.d.ts b/dist/src/index.d.ts new file mode 100644 index 00000000..5c6de812 --- /dev/null +++ b/dist/src/index.d.ts @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2023 + * + * ThoughtSpot Visual Embed SDK for embedding ThoughtSpot analytics + * in other web applications. + * @summary ThoughtSpot Visual Embed SDK + * @author Ayon Ghosh + */ +import { AppEmbed, Page, AppViewConfig, HomePageSearchBarMode, PrimaryNavbarVersion, HomePage, ListPage, DataPanelCustomColumnGroupsAccordionState } from './embed/app'; +import { init, prefetch, logout, executeTML, exportTML, executeTMLInput, exportTMLInput, reloadIframe } from './embed/base'; +import { PinboardEmbed, LiveboardViewConfig, LiveboardEmbed } from './embed/liveboard'; +import { SearchEmbed, SearchViewConfig } from './embed/search'; +import { SearchBarEmbed, SearchBarViewConfig } from './embed/search-bar'; +import { SpotterAgentEmbed, SpotterAgentEmbedViewConfig, BodylessConversation, BodylessConversationViewConfig } from './embed/bodyless-conversation'; +import { SpotterEmbed, SpotterEmbedViewConfig, SpotterChatViewConfig, SpotterSidebarViewConfig, ConversationEmbed, ConversationViewConfig } from './embed/conversation'; +import { SpotterVizConfig, SpotterVizStarterPrompt } from './embed/spotter-viz-utils'; +import { AuthFailureType, AuthStatus, AuthEvent, AuthEventEmitter } from './auth'; +import { getSessionInfo } from './utils/sessionInfoService'; +import { AuthType, RuntimeFilter, RuntimeFilterOp, EmbedEvent, HostEvent, DataSourceVisualMode, Action, EmbedConfig, PrefetchFeatures, FrameParams, DOMSelector, HomeLeftNavItem, HomepageModule, MessageOptions, MessageCallback, MessagePayload, CustomisationsInterface, CustomStyles, customCssInterface, ContextMenuTriggerOptions, RuntimeParameter, LogLevel, VizPoint, CustomActionPayload, ListPageColumns, CustomActionsPosition, CustomActionTarget, InterceptedApiType, EmbedErrorCodes, EmbedErrorDetailsEvent, ErrorDetailsTypes, ContextType, AutoMCPFrameRendererViewConfig, LegendPosition, BackgroundFormatType, ConditionalFormattingComparisonType, ConditionalFormattingOperator, DataLabelFilterOperator, TableTheme, TableContentDensity, VisualizationOverrides } from './types'; +import { CustomCssVariables } from './css-variables'; +import { AnswerService, SessionInterface, UnderlyingDataPoint } from './utils/graphql/answerService/answerService'; +import { getEmbedConfig } from './embed/embedConfig'; +import { uploadMixpanelEvent, MIXPANEL_EVENT } from './mixpanel-service'; +import { tokenizedFetch } from './tokenizedFetch'; +import { getAnswerFromQuery } from './utils/graphql/nlsService/nls-answer-service'; +import { createLiveboardWithAnswers } from './utils/liveboardService/liveboardService'; +import { UIPassthroughEvent } from './embed/hostEventClient/contracts'; +export { init, logout, prefetch, executeTML, exportTML, executeTMLInput, reloadIframe, exportTMLInput, getEmbedConfig as getInitConfig, getSessionInfo, tokenizedFetch, getAnswerFromQuery, createLiveboardWithAnswers, SearchEmbed, SearchBarEmbed, PinboardEmbed, LiveboardEmbed, AppEmbed, SpotterAgentEmbed, SpotterAgentEmbedViewConfig, BodylessConversationViewConfig, BodylessConversation, SpotterEmbed, SpotterEmbedViewConfig, SpotterChatViewConfig, SpotterSidebarViewConfig, ConversationViewConfig, ConversationEmbed, AuthFailureType, AuthStatus, AuthEvent, AuthEventEmitter, AnswerService, SessionInterface, UnderlyingDataPoint, Page, AuthType, RuntimeFilter, RuntimeFilterOp, EmbedEvent, HostEvent, ContextType, DataSourceVisualMode, Action, ContextMenuTriggerOptions, EmbedConfig, SearchViewConfig, SearchBarViewConfig, LiveboardViewConfig, SpotterVizConfig, SpotterVizStarterPrompt, AppViewConfig, PrefetchFeatures, FrameParams, HomeLeftNavItem, HomepageModule, DOMSelector, MessageOptions, MessageCallback, MessagePayload, CustomisationsInterface, CustomStyles, customCssInterface, CustomCssVariables, RuntimeParameter, LogLevel, uploadMixpanelEvent, MIXPANEL_EVENT, HomePageSearchBarMode, PrimaryNavbarVersion, HomePage, ListPage, VizPoint, CustomActionPayload, UIPassthroughEvent, ListPageColumns, DataPanelCustomColumnGroupsAccordionState, CustomActionsPosition, CustomActionTarget, InterceptedApiType, EmbedErrorCodes, EmbedErrorDetailsEvent, ErrorDetailsTypes, AutoMCPFrameRendererViewConfig, VisualizationOverrides, LegendPosition, BackgroundFormatType, ConditionalFormattingComparisonType, ConditionalFormattingOperator, DataLabelFilterOperator, TableTheme, TableContentDensity, }; +export { resetCachedAuthToken } from './authToken'; +export { startAutoMCPFrameRenderer } from './embed/auto-frame-renderer'; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/dist/src/index.d.ts.map b/dist/src/index.d.ts.map new file mode 100644 index 00000000..6193c4eb --- /dev/null +++ b/dist/src/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,OAAO,EACH,QAAQ,EACR,IAAI,EACJ,aAAa,EACb,qBAAqB,EACrB,oBAAoB,EACpB,QAAQ,EACR,QAAQ,EACR,yCAAyC,EAC5C,MAAM,aAAa,CAAC;AACrB,OAAO,EACH,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,UAAU,EACV,SAAS,EACT,eAAe,EACf,cAAc,EACd,YAAY,EACf,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,2BAA2B,EAAE,oBAAoB,EAAE,8BAA8B,EAAC,MAAM,+BAA+B,CAAC;AACpJ,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACxK,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACtF,OAAO,EACH,eAAe,EAAE,UAAU,EAAE,SAAS,EAAE,gBAAgB,EAC3D,MAAM,QAAQ,CAAC;AAChB,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EACH,QAAQ,EACR,aAAa,EACb,eAAe,EACf,UAAU,EACV,SAAS,EACT,oBAAoB,EACpB,MAAM,EACN,WAAW,EACX,gBAAgB,EAChB,WAAW,EACX,WAAW,EACX,eAAe,EACf,cAAc,EACd,cAAc,EACd,eAAe,EACf,cAAc,EACd,uBAAuB,EACvB,YAAY,EACZ,kBAAkB,EAClB,yBAAyB,EACzB,gBAAgB,EAChB,QAAQ,EACR,QAAQ,EACR,mBAAmB,EACnB,eAAe,EACf,qBAAqB,EACrB,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EACf,sBAAsB,EACtB,iBAAiB,EACjB,WAAW,EACX,8BAA8B,EAC9B,cAAc,EACd,oBAAoB,EACpB,mCAAmC,EACnC,6BAA6B,EAC7B,uBAAuB,EACvB,UAAU,EACV,mBAAmB,EACnB,sBAAsB,EACzB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,6CAA6C,CAAC;AACnH,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,+CAA+C,CAAC;AACnF,OAAO,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AACvF,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAEvE,OAAO,EACH,IAAI,EACJ,MAAM,EACN,QAAQ,EACR,UAAU,EACV,SAAS,EACT,eAAe,EACf,YAAY,EACZ,cAAc,EACd,cAAc,IAAI,aAAa,EAC/B,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,0BAA0B,EAC1B,WAAW,EACX,cAAc,EACd,aAAa,EACb,cAAc,EACd,QAAQ,EACR,iBAAiB,EACjB,2BAA2B,EAC3B,8BAA8B,EAC9B,oBAAoB,EACpB,YAAY,EACZ,sBAAsB,EACtB,qBAAqB,EACrB,wBAAwB,EACxB,sBAAsB,EACtB,iBAAiB,EACjB,eAAe,EACf,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,aAAa,EAEb,gBAAgB,EAChB,mBAAmB,EACnB,IAAI,EACJ,QAAQ,EACR,aAAa,EACb,eAAe,EACf,UAAU,EACV,SAAS,EACT,WAAW,EACX,oBAAoB,EACpB,MAAM,EACN,yBAAyB,EACzB,WAAW,EACX,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,uBAAuB,EACvB,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,cAAc,EACd,WAAW,EACX,cAAc,EACd,eAAe,EACf,cAAc,EACd,uBAAuB,EACvB,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,QAAQ,EACR,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,oBAAoB,EACpB,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,EACf,yCAAyC,EACzC,qBAAqB,EACrB,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EACf,sBAAsB,EACtB,iBAAiB,EACjB,8BAA8B,EAC9B,sBAAsB,EACtB,cAAc,EACd,oBAAoB,EACpB,mCAAmC,EACnC,6BAA6B,EAC7B,uBAAuB,EACvB,UAAU,EACV,mBAAmB,GACtB,CAAC;AAEF,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC"} \ No newline at end of file diff --git a/dist/src/mixpanel-service.d.ts b/dist/src/mixpanel-service.d.ts new file mode 100644 index 00000000..ccdd83ab --- /dev/null +++ b/dist/src/mixpanel-service.d.ts @@ -0,0 +1,35 @@ +import { SessionInfo } from './utils/sessionInfoService'; +export declare const EndPoints: { + CONFIG: string; +}; +/** + * Enum of mixpanel events + * @hidden + */ +export declare const MIXPANEL_EVENT: { + VISUAL_SDK_RENDER_START: string; + VISUAL_SDK_CALLED_INIT: string; + VISUAL_SDK_RENDER_COMPLETE: string; + VISUAL_SDK_RENDER_FAILED: string; + VISUAL_SDK_TRIGGER: string; + VISUAL_SDK_ON: string; + VISUAL_SDK_IFRAME_LOAD_PERFORMANCE: string; + VISUAL_SDK_EMBED_CREATE: string; + VERCEL_INTEGRATION_COMPLETED: string; +}; +/** + * Pushes the event with its Property key-value map to mixpanel. + * @param eventId + * @param eventProps + */ +export declare function uploadMixpanelEvent(eventId: string, eventProps?: {}): void; +/** + * + * @param sessionInfo + */ +export declare function initMixpanel(sessionInfo: SessionInfo): void; +/** + * + */ +export declare function testResetMixpanel(): void; +//# sourceMappingURL=mixpanel-service.d.ts.map \ No newline at end of file diff --git a/dist/src/mixpanel-service.d.ts.map b/dist/src/mixpanel-service.d.ts.map new file mode 100644 index 00000000..6845fef3 --- /dev/null +++ b/dist/src/mixpanel-service.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"mixpanel-service.d.ts","sourceRoot":"","sources":["mixpanel-service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAMzD,eAAO,MAAM,SAAS;;CAErB,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;CAU1B,CAAC;AAKF;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,KAAK,GAAG,IAAI,CAM1E;AAeD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI,CA4B3D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,SAGhC"} \ No newline at end of file diff --git a/dist/src/mixpanel-service.spec.d.ts b/dist/src/mixpanel-service.spec.d.ts new file mode 100644 index 00000000..96e9c110 --- /dev/null +++ b/dist/src/mixpanel-service.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=mixpanel-service.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/mixpanel-service.spec.d.ts.map b/dist/src/mixpanel-service.spec.d.ts.map new file mode 100644 index 00000000..e9c35b4b --- /dev/null +++ b/dist/src/mixpanel-service.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"mixpanel-service.spec.d.ts","sourceRoot":"","sources":["mixpanel-service.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/react/all-types-export.d.ts b/dist/src/react/all-types-export.d.ts new file mode 100644 index 00000000..04a898d5 --- /dev/null +++ b/dist/src/react/all-types-export.d.ts @@ -0,0 +1,3 @@ +export { SearchEmbed, PreRenderedSearchEmbed, LiveboardEmbed, PreRenderedLiveboardEmbed, SearchBarEmbed, PreRenderedSearchBarEmbed, AppEmbed, PreRenderedAppEmbed, SpotterEmbed, ConversationEmbed, PreRenderedConversationEmbed, SpotterMessage, useSpotterAgent, useEmbedRef, useInit, } from './index'; +export { init, logout, prefetch, reloadIframe, getInitConfig, getSessionInfo, uploadMixpanelEvent, PinboardEmbed, AuthFailureType, AuthStatus, AuthEvent, AuthEventEmitter, Page, AuthType, RuntimeFilter, RuntimeFilterOp, EmbedEvent, HostEvent, DataSourceVisualMode, Action, EmbedConfig, SearchViewConfig, SearchBarViewConfig, LiveboardViewConfig, AppViewConfig, PrefetchFeatures, FrameParams, DOMSelector, MessageOptions, MessageCallback, MessagePayload, CustomisationsInterface, CustomStyles, customCssInterface, CustomCssVariables, RuntimeParameter, resetCachedAuthToken, UIPassthroughEvent, DataPanelCustomColumnGroupsAccordionState, InterceptedApiType, CustomActionsPosition, CustomActionTarget, } from '../index'; +//# sourceMappingURL=all-types-export.d.ts.map \ No newline at end of file diff --git a/dist/src/react/all-types-export.d.ts.map b/dist/src/react/all-types-export.d.ts.map new file mode 100644 index 00000000..2a8af242 --- /dev/null +++ b/dist/src/react/all-types-export.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"all-types-export.d.ts","sourceRoot":"","sources":["all-types-export.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,WAAW,EACX,sBAAsB,EACtB,cAAc,EACd,yBAAyB,EACzB,cAAc,EACd,yBAAyB,EACzB,QAAQ,EACR,mBAAmB,EACnB,YAAY,EACZ,iBAAiB,EACjB,4BAA4B,EAC5B,cAAc,EACd,eAAe,EACf,WAAW,EACX,OAAO,GACV,MAAM,SAAS,CAAC;AAEjB,OAAO,EACH,IAAI,EACJ,MAAM,EACN,QAAQ,EACR,YAAY,EACZ,aAAa,EACb,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,UAAU,EACV,SAAS,EACT,gBAAgB,EAEhB,IAAI,EACJ,QAAQ,EACR,aAAa,EACb,eAAe,EACf,UAAU,EACV,SAAS,EACT,oBAAoB,EACpB,MAAM,EACN,WAAW,EACX,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,WAAW,EACX,cAAc,EACd,eAAe,EACf,cAAc,EACd,uBAAuB,EACvB,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,EAClB,yCAAyC,EACzC,kBAAkB,EAClB,qBAAqB,EACrB,kBAAkB,GACrB,MAAM,UAAU,CAAC"} \ No newline at end of file diff --git a/dist/src/react/all-types-export.spec.d.ts b/dist/src/react/all-types-export.spec.d.ts new file mode 100644 index 00000000..1010d218 --- /dev/null +++ b/dist/src/react/all-types-export.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=all-types-export.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/react/all-types-export.spec.d.ts.map b/dist/src/react/all-types-export.spec.d.ts.map new file mode 100644 index 00000000..f9e89322 --- /dev/null +++ b/dist/src/react/all-types-export.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"all-types-export.spec.d.ts","sourceRoot":"","sources":["all-types-export.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/react/index.d.ts b/dist/src/react/index.d.ts new file mode 100644 index 00000000..c48cac6e --- /dev/null +++ b/dist/src/react/index.d.ts @@ -0,0 +1,336 @@ +import React from 'react'; +import { AuthEventEmitter } from '../auth'; +import { SearchBarEmbed as _SearchBarEmbed, SearchBarViewConfig } from '../embed/search-bar'; +import { SearchEmbed as _SearchEmbed, SearchViewConfig } from '../embed/search'; +import { AppEmbed as _AppEmbed, AppViewConfig } from '../embed/app'; +import { LiveboardEmbed as _LiveboardEmbed, LiveboardViewConfig } from '../embed/liveboard'; +import { SpotterAgentEmbedViewConfig, ConversationMessage as _ConversationMessage, SpotterAgentMessageViewConfig } from '../embed/bodyless-conversation'; +import { EmbedConfig } from '../types'; +import { EmbedProps } from './util'; +import { SpotterEmbed as _SpotterEmbed, SpotterEmbedViewConfig, ConversationEmbed as _ConversationEmbed, ConversationViewConfig } from '../embed/conversation'; +interface SearchProps extends EmbedProps, SearchViewConfig { +} +interface PreRenderProps { + /** + * PreRender id to be used for PreRendering the embed. + * Use PreRender to render the embed in the background and then + * show or hide the rendered embed using showPreRender or hidePreRender respectively. + * + * Use PreRendered react component for pre rendering embed components. + * @version SDK: 1.25.0 | ThoughtSpot: 9.6.0.cl + * @example + * ```js + * const embed = new LiveboardEmbed('#embed', { + * ... // other liveboard view config + * preRenderId: "preRenderId-123" + * }); + * embed.showPreRender(); + * ``` + * @example + * ```tsx + * function LandingPageComponent() { + * return + * } + * ``` + * function MyComponent() { + * return + * } + * ``` + */ + preRenderId: string; +} +/** + * React component for Search Embed. + * @example + * ```tsx + * function Search() { + * return + * } + * ``` + */ +export declare const SearchEmbed: React.ForwardRefExoticComponent>; +export declare const PreRenderedSearchEmbed: React.ForwardRefExoticComponent>; +interface AppProps extends EmbedProps, AppViewConfig { +} +/** + * React component for Full app Embed. + * @example + * ```tsx + * function App() { + * return console.error(error)} + * /> + * } + * ``` + */ +export declare const AppEmbed: React.ForwardRefExoticComponent>; +/** + * React component for PreRendered App embed. + * + * PreRenderedAppEmbed will preRender the AppEmbed and will be hidden by + * default. + * + * AppEmbed with preRenderId passed will call showPreRender on the embed. + * @example + * ```tsx + * function LandingPageComponent() { + * return + * } + * ``` + * function MyComponent() { + * return + * } + * ``` + */ +export declare const PreRenderedAppEmbed: React.ForwardRefExoticComponent>; +interface LiveboardProps extends EmbedProps, LiveboardViewConfig { +} +/** + * React component for Liveboard embed. + * @example + * ```tsx + * function Liveboard() { + * return console.log('Liveboard rendered')} + * vizId="vizId" {/* if doing viz embed *\/} + * /> + * } + * ``` + */ +export declare const LiveboardEmbed: React.ForwardRefExoticComponent>; +export declare const PinboardEmbed: React.ForwardRefExoticComponent>; +/** + * React component for PreRendered Liveboard embed. + * + * PreRenderedLiveboardEmbed will preRender the liveboard and will be hidden by default. + * + * LiveboardEmbed with preRenderId passed will call showPreRender on the embed. + * + * If LiveboardEmbed is rendered before PreRenderedLiveboardEmbed is rendered it + * tries to preRender the LiveboardEmbed, so it is recommended to pass the + * liveboardId to both the components. + * @example + * ```tsx + * function LandingPageComponent() { + * return + * } + * ``` + * function MyComponent() { + * return + * } + * ``` + */ +export declare const PreRenderedLiveboardEmbed: React.ForwardRefExoticComponent>; +export declare const PreRenderedPinboardEmbed: React.ForwardRefExoticComponent>; +interface SearchBarEmbedProps extends EmbedProps, SearchBarViewConfig { +} +/** + * React component for Search bar embed. + * @example + * ```tsx + * function SearchBar() { + * return + * } + * ``` + */ +export declare const SearchBarEmbed: React.ForwardRefExoticComponent>; +/** + * React component for PreRendered SearchBar embed. + * + * PreRenderedSearchBarEmbed will preRender the SearchBarEmbed and will be hidden by + * default. + * + * SearchBarEmbed with preRenderId passed will call showPreRender on the embed. + * @example + * ```tsx + * function LandingPageComponent() { + * return + * } + * ``` + * function MyComponent() { + * return + * } + * ``` + */ +export declare const PreRenderedSearchBarEmbed: React.ForwardRefExoticComponent>; +interface SpotterEmbedProps extends EmbedProps, SpotterEmbedViewConfig { +} +interface ConversationEmbedProps extends EmbedProps, ConversationViewConfig { +} +/** + * React component for LLM based conversation BI. + * @example + * ```tsx + * function Sage() { + * return " + * }} + * ... other view config props or event listeners. + * /> + * } + * ``` + */ +export declare const SpotterEmbed: React.ForwardRefExoticComponent>; +/** + * React component for LLM based conversation BI. + * Use {@link SpotterEmbed} instead + * @deprecated from SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + * @example + * ```tsx + * function Sage() { + * return " + * }} + * ... other view config props or event listeners. + * /> + * } + * ``` + */ +export declare const ConversationEmbed: React.ForwardRefExoticComponent>; +/** + * React component for individual conversation messages from SpotterAgent. + * This component is used internally by the useSpotterAgent hook. + * @version SDK: 1.37.0 | ThoughtSpot: 10.9.0.cl + */ +interface ConversationMessageProps extends EmbedProps, SpotterAgentMessageViewConfig { +} +export declare const ConversationMessage: React.ForwardRefExoticComponent>; +/** + * React component for displaying individual conversation messages from SpotterAgent. + * + * This component renders a single message response from your ThoughtSpot conversation, + * showing charts, visualizations, or text responses based on the user's query. + * + * @version SDK: 1.39.0 | ThoughtSpot: 10.11.0.cl + * @example + * ```tsx + * const { sendMessage } = useSpotterAgent({ worksheetId: 'worksheetId' }); + * const result = await sendMessage('show me sales by region'); + * + * if (!result.error) { + * // Simple usage - just pass the message data + * + * + * // With optional query for context + * + * } + * ``` + */ +export declare const SpotterMessage: React.ForwardRefExoticComponent<{ + message: SpotterAgentMessageViewConfig; + query?: string; +} & Omit & React.RefAttributes<_ConversationMessage>>; +/** + * React component for PreRendered Conversation embed. + * + * PreRenderedConversationEmbed will preRender the SpotterEmbed and will be hidden by + * default. + * + * SpotterEmbed with preRenderId passed will call showPreRender on the embed. + * @example + * ```tsx + * function LandingPageComponent() { + * return + * } + * ``` + * function MyComponent() { + * return + * } + * ``` + */ +export declare const PreRenderedConversationEmbed: React.ForwardRefExoticComponent>; +type EmbedComponent = typeof SearchEmbed | typeof AppEmbed | typeof LiveboardEmbed | typeof SearchBarEmbed | typeof ConversationMessage | typeof SpotterMessage | typeof SpotterEmbed | typeof ConversationEmbed; +/** + * Get a reference to the embed component to trigger events on the component. + * @example + * ``` + * function Component() { + * const ref = useEmbedRef(); + * useEffect(() => { + * ref.current.trigger( + * EmbedEvent.UpdateRuntimeFilter, + * [{ columnName: 'name', operator: 'EQ', values: ['value']}]); + * }, []) + * return } /> + * } + * ``` + * @returns {React.MutableRefObject} ref + */ +export declare function useEmbedRef(): React.MutableRefObject>; +/** + * + * @version SDK: 1.36.2 | ThoughtSpot: * + * @param config - EmbedConfig + * @returns AuthEventEmitter + * @example + * ``` + * function Component() { + * const authEE = useInit({ ...initConfig }); + * return } /> + * } + * ``` + */ +export declare function useInit(config: EmbedConfig): React.MutableRefObject; +/** + * React hook for interacting with SpotterAgent AI conversations. + * + * This hook provides a sendMessage function that allows you to send natural language + * queries to your data and get back AI-generated responses with visualizations. + * + * @version SDK: 1.39.0 | ThoughtSpot: 10.11.0.cl + * @param config - Configuration object containing worksheetId and other options + * @returns Object with sendMessage function that returns conversation results + * @example + * ```tsx + * const { sendMessage } = useSpotterAgent({ worksheetId: 'worksheetId' }); + * + * const handleQuery = async () => { + * const result = await sendMessage('show me sales by region'); + * + * if (!result.error) { + * // Display the message response + * + * } else { + * console.error('Error:', result.error); + * } + * }; + * ``` + */ +export declare function useSpotterAgent(config: SpotterAgentEmbedViewConfig): { + sendMessage: (query: string) => Promise<{ + error: any; + query?: undefined; + message?: undefined; + } | { + query: string; + message: { + worksheetId: string; + convId: any; + messageId: any; + sessionId: any; + genNo: any; + acSessionId: any; + acGenNo: any; + }; + error?: undefined; + }>; +}; +export { LiveboardViewConfig, SearchViewConfig, AppViewConfig, Page, RuntimeFilter, RuntimeFilterOp, EmbedEvent, HostEvent, Action, FrameParams, HomeLeftNavItem, HomepageModule, LogLevel, getSessionInfo, ListPageColumns, CustomActionsPosition, } from '../index'; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/dist/src/react/index.d.ts.map b/dist/src/react/index.d.ts.map new file mode 100644 index 00000000..ec0847c4 --- /dev/null +++ b/dist/src/react/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAEnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAE3C,OAAO,EAAE,cAAc,IAAI,eAAe,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC7F,OAAO,EAAE,WAAW,IAAI,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAChF,OAAO,EAAE,QAAQ,IAAI,SAAS,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,cAAc,IAAI,eAAe,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE5F,OAAO,EAA2C,2BAA2B,EAAE,mBAAmB,IAAI,oBAAoB,EAAE,6BAA6B,EAAE,MAAM,gCAAgC,CAAC;AAElM,OAAO,EAAE,WAAW,EAAkC,MAAM,UAAU,CAAC;AACvE,OAAO,EAAE,UAAU,EAA4B,MAAM,QAAQ,CAAC;AAC9D,OAAO,EAAE,YAAY,IAAI,aAAa,EAAE,sBAAsB,EAAE,iBAAiB,IAAI,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAgG/J,UAAU,WAAY,SAAQ,UAAU,EAAE,gBAAgB;CAAI;AAE9D,UAAU,cAAc;IACpB;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,WAAW,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,WAAW,kFAEvB,CAAC;AAEF,eAAO,MAAM,sBAAsB,mGAId,CAAC;AAEtB,UAAU,QAAS,SAAQ,UAAU,EAAE,aAAa;CAAI;AAExD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,QAAQ,4EAAyE,CAAC;AAE/F;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,mBAAmB,6FAId,CAAC;AAEnB,UAAU,cAAe,SAAQ,UAAU,EAAE,mBAAmB;CAAI;AAEpE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,cAAc,wFAIT,CAAC;AAEnB,eAAO,MAAM,aAAa,wFAAiB,CAAC;AAE5C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,yBAAyB,yGAId,CAAC;AAEzB,eAAO,MAAM,wBAAwB,yGAA4B,CAAC;AAElE,UAAU,mBAAoB,SAAQ,UAAU,EAAE,mBAAmB;CAAI;AAEzE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,6FAIT,CAAC;AAEnB;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,yBAAyB,8GAId,CAAC;AAEzB,UAAU,iBAAkB,SAAQ,UAAU,EAAE,sBAAsB;CAAI;AAC1E,UAAU,sBAAuB,SAAQ,UAAU,EAAE,sBAAsB;CAAI;AAE/E;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,YAAY,yFAIT,CAAC;AAGjB;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,iBAAiB,mGAIT,CAAC;AAEtB;;;;GAIG;AACH,UAAU,wBAAyB,SAAQ,UAAU,EAAE,6BAA6B;CAAG;AAEvF,eAAO,MAAM,mBAAmB,uGAIT,CAAC;AAOxB;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,cAAc;aA5Bd,6BAA6B;YAC9B,MAAM;sGAwChB,CAAC;AAGH;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,4BAA4B,0GAInB,CAAC;AAEvB,KAAK,cAAc,GAAG,OAAO,WAAW,GAClC,OAAO,QAAQ,GACf,OAAO,cAAc,GACrB,OAAO,cAAc,GACrB,OAAO,mBAAmB,GAC1B,OAAO,cAAc,GACrB,OAAO,YAAY,GACnB,OAAO,iBAAiB,CAAC;AAE/B;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,cAAc,KAChD,KAAK,CAAC,gBAAgB,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAEhD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,WAAW,4CAQ1C;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,2BAA2B;yBAejB,MAAM;;;;;;;;;;;;;;;;;EAuBvD;AAED,OAAO,EACH,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,EACb,IAAI,EACJ,aAAa,EACb,eAAe,EACf,UAAU,EACV,SAAS,EACT,MAAM,EACN,WAAW,EACX,eAAe,EACf,cAAc,EACd,QAAQ,EACR,cAAc,EACd,eAAe,EACf,qBAAqB,GACxB,MAAM,UAAU,CAAC"} \ No newline at end of file diff --git a/dist/src/react/index.spec.d.ts b/dist/src/react/index.spec.d.ts new file mode 100644 index 00000000..f379e73a --- /dev/null +++ b/dist/src/react/index.spec.d.ts @@ -0,0 +1,3 @@ +import '@testing-library/jest-dom'; +import '@testing-library/jest-dom/extend-expect'; +//# sourceMappingURL=index.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/react/index.spec.d.ts.map b/dist/src/react/index.spec.d.ts.map new file mode 100644 index 00000000..bb6eff5c --- /dev/null +++ b/dist/src/react/index.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.spec.d.ts","sourceRoot":"","sources":["index.spec.tsx"],"names":[],"mappings":"AACA,OAAO,2BAA2B,CAAC;AACnC,OAAO,yCAAyC,CAAC"} \ No newline at end of file diff --git a/dist/src/react/util.d.ts b/dist/src/react/util.d.ts new file mode 100644 index 00000000..2b003bbf --- /dev/null +++ b/dist/src/react/util.d.ts @@ -0,0 +1,24 @@ +/// +import { EmbedEvent, MessageCallback, AllEmbedViewConfig } from '../types'; +interface EmbedViewConfig extends AllEmbedViewConfig, EmbedEventHandlers { +} +export type EmbedEventHandlers = { + [key in keyof typeof EmbedEvent as `on${Capitalize}`]?: MessageCallback; +}; +export interface EmbedProps extends EmbedViewConfig { + className?: string; + style?: React.CSSProperties; +} +export interface ViewConfigAndListeners { + viewConfig: T; + listeners: { + [key in EmbedEvent]?: MessageCallback; + }; +} +/** + * + * @param props + */ +export declare function getViewPropsAndListeners(props: T): ViewConfigAndListeners; +export {}; +//# sourceMappingURL=util.d.ts.map \ No newline at end of file diff --git a/dist/src/react/util.d.ts.map b/dist/src/react/util.d.ts.map new file mode 100644 index 00000000..8f96d7f9 --- /dev/null +++ b/dist/src/react/util.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["util.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAE3E,UAAU,eAAgB,SAAQ,kBAAkB,EAAE,kBAAkB;CAAG;AAE3E,MAAM,MAAM,kBAAkB,GAAG;KAAG,GAAG,IAAI,MAAM,OAAO,UAAU,IAAI,KAAK,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,eAAe;CAAE,CAAC;AAElH,MAAM,WAAW,UAAW,SAAQ,eAAe;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC/B;AAED,MAAM,WAAW,sBAAsB,CAAC,CAAC,SAAS,eAAe;IAC7D,UAAU,EAAE,CAAC,CAAC;IACd,SAAS,EAAE;SAAG,GAAG,IAAI,UAAU,CAAC,CAAC,EAAE,eAAe;KAAE,CAAC;CACxD;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACpC,CAAC,SAAS,UAAU,EACpB,CAAC,SAAS,eAAe,EAAE,KAAK,EAAE,CAAC,GAAG,sBAAsB,CAAC,CAAC,CAAC,CAgBlE"} \ No newline at end of file diff --git a/dist/src/react/util.spec.d.ts b/dist/src/react/util.spec.d.ts new file mode 100644 index 00000000..cf75057f --- /dev/null +++ b/dist/src/react/util.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=util.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/react/util.spec.d.ts.map b/dist/src/react/util.spec.d.ts.map new file mode 100644 index 00000000..5cc9bbd1 --- /dev/null +++ b/dist/src/react/util.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"util.spec.d.ts","sourceRoot":"","sources":["util.spec.tsx"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/test/test-utils.d.ts b/dist/src/test/test-utils.d.ts new file mode 100644 index 00000000..b646fbcb --- /dev/null +++ b/dist/src/test/test-utils.d.ts @@ -0,0 +1,62 @@ +export declare const defaultParamsWithoutHiddenActions: string; +export declare const defaultParams: string; +export declare const defaultParamsForPinboardEmbed: string; +export declare const getDocumentBody: () => string; +type DOMElement = HTMLElement | Document; +export declare const getRootEl: () => HTMLElement; +export declare const getRootEl2: () => HTMLElement; +export declare const getIFrameEl: (container?: DOMElement) => HTMLIFrameElement; +export declare const getAllIframeEl: () => NodeListOf; +export declare const getIFrameSrc: (container?: DOMElement) => string; +export declare const waitFor: (fn: () => boolean) => Promise; +/** + * jsdom does not set event source, therefore we do it + * programmatically and use dispatchEvent instead of the + * postMessage API + * Reference: https://github.com/jsdom/jsdom/issues/2745 + * @param window + * @param data + * @param port + */ +export declare const postMessageToParent: (window: WindowProxy, data: any, port?: any) => void; +/** + * Execute a given function after a certain time has elapsed + * @param fn The function to be executed after the wait period + * @param waitTime The wait period in milliseconds + */ +export declare const executeAfterWait: (fn: (...args: any[]) => void, waitTime?: number) => Promise; +/** + * Time (in milliseconds) to wait for async events to be triggered + */ +export declare const EVENT_WAIT_TIME = 1000; +/** + * + * @param str + */ +export declare function fixedEncodeURI(str: string): string; +/** + * MessageChannel is available in Node > 15.0.0. Since the current node + * environment's used for github actions is not above 14, we are mocking this + * for the current unit tests. + */ +export declare const messageChannelMock: any; +export declare const mockMessageChannel: () => void; +export declare const expectUrlMatchesWithParams: (source: string, target: string) => void; +export declare const expectUrlToHaveParamsWithValues: (url: string, paramsWithValues: Record) => void; +export declare const expectUrlMatch: (source: string, target: string) => void; +export declare const createRootEleForEmbed: () => void; +export declare const getHashQueryParams: (hash: string) => any; +export declare const mockSessionInfo: { + userGUID: string; + mixpanelToken: string; + isPublicUser: boolean; + sessionId: string; + genNo: number; + acSession: { + sessionId: string; + genNo: number; + }; +}; +export declare const testVisualOverridesInEmbed: (embed: any, visualOverrides: any) => Promise; +export {}; +//# sourceMappingURL=test-utils.d.ts.map \ No newline at end of file diff --git a/dist/src/test/test-utils.d.ts.map b/dist/src/test/test-utils.d.ts.map new file mode 100644 index 00000000..f27a1b95 --- /dev/null +++ b/dist/src/test/test-utils.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["test-utils.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,iCAAiC,QAAgJ,CAAC;AAC/L,eAAO,MAAM,aAAa,QAAmF,CAAC;AAC9G,eAAO,MAAM,6BAA6B,QAA6K,CAAC;AACxN,eAAO,MAAM,eAAe,cAAyD,CAAC;AAEtF,KAAK,UAAU,GAAG,WAAW,GAAG,QAAQ,CAAC;AAEzC,eAAO,MAAM,SAAS,mBAAyC,CAAC;AAEhE,eAAO,MAAM,UAAU,mBAA2C,CAAC;AAEnE,eAAO,MAAM,WAAW,eAAe,UAAU,sBAAiD,CAAC;AAEnG,eAAO,MAAM,cAAc,qCAA4C,CAAC;AAExE,eAAO,MAAM,YAAY,eAAe,UAAU,WAA0C,CAAC;AAE7F,eAAO,MAAM,OAAO,OAAQ,MAAM,OAAO,KAAG,QAAQ,IAAI,CAQtD,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,WAAY,WAAW,QAAQ,GAAG,SAAS,GAAG,SAO7E,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,iBACX,GAAG,EAAE,KAAK,IAAI,wCAO9B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,eAAe,OAAO,CAAC;AAEpC;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,UAEzC;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,EAAE,GAGhC,CAAC;AACF,eAAO,MAAM,kBAAkB,YAM9B,CAAC;AAEF,eAAO,MAAM,0BAA0B,WAAY,MAAM,UAAU,MAAM,SAYxE,CAAC;AAEF,eAAO,MAAM,+BAA+B,QACnC,MAAM,oBAAoB,OAAO,MAAM,EAAE,GAAG,CAAC,SAcrD,CAAC;AAEF,eAAO,MAAM,cAAc,WAAY,MAAM,UAAU,MAAM,SAK5D,CAAC;AAEF,eAAO,MAAM,qBAAqB,YAOjC,CAAC;AAEF,eAAO,MAAM,kBAAkB,SAAU,MAAM,KAAG,GAIjD,CAAC;AAEF,eAAO,MAAM,eAAe;;;;;;;;;;CAU3B,CAAC;AAEF,eAAO,MAAM,0BAA0B,UAC5B,GAAG,mBACO,GAAG,kBA4BvB,CAAC"} \ No newline at end of file diff --git a/dist/src/tokenizedFetch.d.ts b/dist/src/tokenizedFetch.d.ts new file mode 100644 index 00000000..7f4448de --- /dev/null +++ b/dist/src/tokenizedFetch.d.ts @@ -0,0 +1,17 @@ +/** + * Fetch wrapper that adds the authentication token to the request. + * Use this to call the ThoughtSpot APIs when using the visual embed sdk. + * The interface for this method is the same as Web `Fetch`. + * @param input + * @param init + * @example + * ```js + * tokenizedFetch("/api/rest/2.0/auth/session/user", { + * // .. fetch options .. + * }); + * ``` + * @version SDK: 1.28.0 + * @group Global methods + */ +export declare const tokenizedFetch: typeof fetch; +//# sourceMappingURL=tokenizedFetch.d.ts.map \ No newline at end of file diff --git a/dist/src/tokenizedFetch.d.ts.map b/dist/src/tokenizedFetch.d.ts.map new file mode 100644 index 00000000..d8292194 --- /dev/null +++ b/dist/src/tokenizedFetch.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"tokenizedFetch.d.ts","sourceRoot":"","sources":["tokenizedFetch.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,cAAc,EAAE,OAAO,KAmBnC,CAAC"} \ No newline at end of file diff --git a/dist/src/tokenizedFetch.spec.d.ts b/dist/src/tokenizedFetch.spec.d.ts new file mode 100644 index 00000000..e865a2a7 --- /dev/null +++ b/dist/src/tokenizedFetch.spec.d.ts @@ -0,0 +1,2 @@ +import 'jest-fetch-mock'; +//# sourceMappingURL=tokenizedFetch.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/tokenizedFetch.spec.d.ts.map b/dist/src/tokenizedFetch.spec.d.ts.map new file mode 100644 index 00000000..c5e12738 --- /dev/null +++ b/dist/src/tokenizedFetch.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"tokenizedFetch.spec.d.ts","sourceRoot":"","sources":["tokenizedFetch.spec.ts"],"names":[],"mappings":"AAAA,OAAO,iBAAiB,CAAC"} \ No newline at end of file diff --git a/dist/src/types.d.ts b/dist/src/types.d.ts new file mode 100644 index 00000000..d0fd0fe3 --- /dev/null +++ b/dist/src/types.d.ts @@ -0,0 +1,8725 @@ +/** + * Copyright (c) 2023 + * + * TypeScript type definitions for ThoughtSpot Visual Embed SDK + * @summary Type definitions for Embed SDK + * @author Ayon Ghosh + */ +import { CustomCssVariables } from './css-variables'; +import type { SessionInterface } from './utils/graphql/answerService/answerService'; +/** + * The authentication mechanism for allowing access to + * the embedded app + * @group Authentication / Init + */ +export declare enum AuthType { + /** + * No authentication on the SDK. Pass-through to the embedded App. Alias for + * `Passthrough`. + * @example + * ```js + * init({ + * // ... + * authType: AuthType.None, + * }); + * ``` + */ + None = "None", + /** + * Passthrough SSO to the embedded application within the iframe. Requires least + * configuration, but may not be supported by all IDPs. This will behave like `None` + * if SSO is not configured on ThoughtSpot. + * + * To use this: + * Your SAML or OpenID provider must allow iframe redirects. + * For example, if you are using Okta as IdP, you can enable iframe embedding. + * @version SDK: 1.15.0 | ThoughtSpot: 8.8.0.cl + * @example + * ```js + * init({ + * // ... + * authType: AuthType.EmbeddedSSO, + * }); + * ``` + */ + EmbeddedSSO = "EmbeddedSSO", + /** + * SSO using SAML, Use {@link SAMLRedirect} instead + * @deprecated This option is deprecated. + * @hidden + */ + SSO = "SSO_SAML", + /** + * SSO using SAML, Use {@link SAMLRedirect} instead + * @deprecated This option is deprecated. + * @hidden + */ + SAML = "SSO_SAML", + /** + * SSO using SAML + * Makes the host application redirect to the SAML IdP. Use this + * if your IdP does not allow itself to be embedded. + * + * This redirects the host application to the SAML IdP. The host application + * will be redirected back to the ThoughtSpot app after authentication. + * @example + * ```js + * init({ + * // ... + * authType: AuthType.SAMLRedirect, + * }); + * ``` + * + * This opens the SAML IdP in a popup window. The popup is triggered + * when the user clicks the trigger button. The popup window will be + * closed automatically after authentication. + * @example + * ```js + * init({ + * // ... + * authType: AuthType.SAMLRedirect, + * authTriggerText: 'Login with SAML', + * authTriggerContainer: '#tsEmbed', + * inPopup: true, + * }); + * ``` + * + * Can also use the event to trigger the popup flow. Works the same + * as the above example. + * @example + * ```js + * const authEE = init({ + * // ... + * authType: AuthType.SAMLRedirect, + * inPopup: true, + * }); + * + * someButtonOnYourPage.addEventListener('click', () => { + * authEE.emit(AuthEvent.TRIGGER_SSO_POPUP); + * }); + * ``` + */ + SAMLRedirect = "SSO_SAML", + /** + * SSO using OIDC + * SSO using OIDC, Use {@link OIDCRedirect} instead + * @deprecated This option is deprecated. + * @hidden + */ + OIDC = "SSO_OIDC", + /** + * SSO using OIDC + * Will make the host application redirect to the OIDC IdP. + * See code samples in {@link SAMLRedirect}. + */ + OIDCRedirect = "SSO_OIDC", + /** + * Trusted authentication server + * Use {@link TrustedAuth} instead + * @deprecated This option is deprecated. + * @hidden + */ + AuthServer = "AuthServer", + /** + * Trusted authentication server. Use your own authentication server + * which returns a bearer token, generated using the `secret_key` obtained + * from ThoughtSpot. + * @example + * ```js + * init({ + * // ... + * authType: AuthType.TrustedAuthToken, + * getAuthToken: () => { + * return fetch('https://my-backend.app/ts-token') + * .then((response) => response.json()) + * .then((data) => data.token); + * } + * }); + * ``` + */ + TrustedAuthToken = "AuthServer", + /** + * Trusted authentication server Cookieless, Use your own authentication + * server which returns a bearer token, generated using the `secret_key` + * obtained from ThoughtSpot. This uses a cookieless authentication + * approach, recommended to bypass the third-party cookie-blocking restriction + * implemented by some browsers. + * @version SDK: 1.22.0 | ThoughtSpot: 9.3.0.cl, 9.5.1.sw + * @example + * ```js + * init({ + * // ... + * authType: AuthType.TrustedAuthTokenCookieless, + * getAuthToken: () => { + * return fetch('https://my-backend.app/ts-token') + * .then((response) => response.json()) + * .then((data) => data.token); + * } + * }); + * ``` + */ + TrustedAuthTokenCookieless = "AuthServerCookieless", + /** + * Use the ThoughtSpot login API to authenticate to the cluster directly. + * + * Warning: This feature is primarily intended for developer testing. It is + * strongly advised not to use this authentication method in production. + */ + Basic = "Basic" +} +/** + * + * **Note**: This attribute is not supported in the classic (V1) homepage experience. + * + */ +export declare enum HomeLeftNavItem { + /** + * The *Search data* option in + * the *Insights* left navigation panel. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + */ + SearchData = "search-data", + /** + * The *Home* menu option in + * the *Insights* left navigation panel. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + */ + Home = "insights-home", + /** + * The *Liveboards* menu option in + * the *Insights* left navigation panel. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + */ + Liveboards = "liveboards", + /** + * The *Answers* menu option in + * the *Insights* left navigation panel. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + */ + Answers = "answers", + /** + * The *Monitor subscriptions* menu option in + * the *Insights* left navigation panel. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + */ + MonitorSubscription = "monitor-alerts", + /** + * The *SpotIQ analysis* menu option in + * the *Insights* left navigation panel. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + */ + SpotIQAnalysis = "spotiq-analysis", + /** + * The *Liveboard schedules* menu option in + * the *Insights* left navigation panel. + * @version SDK: 1.34.0 | ThoughtSpot: 10.3.0.cl + */ + LiveboardSchedules = "liveboard-schedules", + /** + * The create option in the *Insights* + * left navigation panel. + * Available in the V3 navigation experience. + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + Create = "create", + /** + * The *Spotter* menu option in the *Insights* + * left navigation panel. + * Available in the V3 navigation experience. + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + Spotter = "spotter", + /** + * The *Favorites* section in the *Insights* + * left navigation panel. + * Available in the V3 navigation experience. + * @version SDK: 1.41.0 | ThoughtSpot: 10.12.0.cl + */ + Favorites = "favorites" +} +export type DOMSelector = string | HTMLElement; +/** + * inline customCSS within the {@link CustomisationsInterface}. + * Use {@link CustomCssVariables} or css rules. + */ +export interface customCssInterface { + /** + * The custom css variables, which can be set. + * The variables are available in the {@link CustomCssVariables} + * interface. For more information, see + * link:https://developers.thoughtspot.com/docs/css-variables-reference[CSS variable reference]. + */ + variables?: CustomCssVariables; + /** + * Can be used to define a custom font face + * like: + * @example + * ```js + * rules_UNSTABLE?: { + * "@font-face": { + * "font-family": "custom-font", + * "src": url("/path/") + * }; + * }; + * ``` + * + * Also, custom css rules outside of variables. + * @example + * ```js + * rules_UNSTABLE?: { + * ".thoughtspot_class_name": { + * "border-radius": "10px", + * margin: "20px" + * }; + * }; + * ``` + */ + rules_UNSTABLE?: { + [selector: string]: { + [declaration: string]: string; + }; + }; +} +/** + * Styles within the {@link CustomisationsInterface}. + */ +export interface CustomStyles { + customCSSUrl?: string; + customCSS?: customCssInterface; +} +/** + * Configuration to define the customization on the Embedded + * ThoughtSpot components. + * You can customize styles, text strings, and icons. + * For more information, see link:https://developers.thoughtspot.com/docs/custom-css[CSS customization framework]. + * @example + * ```js + * init({ + * // ... + * customizations: { + * style: { + * customCSS: { + * variables: {}, + * rules_UNSTABLE: {} + * } + * }, + * content: { + * strings: { + * 'LIVEBOARDS': 'Dashboards', + * 'ANSWERS': 'Visualizations', + * 'Edit': 'Modify', + * 'Show underlying data': 'Show source data', + * 'SpotIQ': 'Insights', + * 'Monitor': 'Alerts', + * } + * }, + * iconSpriteUrl: 'https://my-custom-icon-sprite.svg' + * } + * }) + * ``` + */ +export interface CustomisationsInterface { + style?: CustomStyles; + content?: { + /** + * @version SDK: 1.26.0 | 9.7.0.cl + */ + strings?: Record; + stringIDs?: Record; + stringIDsUrl?: string; + [key: string]: any; + }; + iconSpriteUrl?: string; +} +/** + * The configuration object for embedding ThoughtSpot content. + * It includes the ThoughtSpot hostname or IP address, + * the type of authentication, and the authentication endpoint + * if a trusted authentication server is used. + * @group Authentication / Init + */ +export interface EmbedConfig { + /** + * The ThoughtSpot cluster hostname or IP address. + */ + thoughtSpotHost: string; + /** + * The authentication mechanism to use. + */ + authType: AuthType; + /** + * [AuthServer] The trusted authentication endpoint to use to get the + * authentication token. A `GET` request is made to the + * authentication API endpoint, which returns the token + * as a plaintext response. For trusted authentication, + * the `authEndpoint` or `getAuthToken` attribute is required. + */ + authEndpoint?: string; + /** + * [AuthServer] A function that invokes the trusted authentication endpoint + * and returns a Promise that resolves to the `auth token` string. + * For trusted authentication, the `authEndpoint` or `getAuthToken` + * attribute is required. + * + * It is advisable to fetch a new token inside this method and not + * reuse the old issued token. When auth expires this method is + * called again and if it is called with an older token, the authentication + * will not succeed. + */ + getAuthToken?: () => Promise; + /** + * [AuthServer / Basic] The user name of the ThoughtSpot user. This + * attribute is required for trusted authentication. + */ + username?: string; + /** + * [Basic] The ThoughtSpot login password corresponding to the username + * + * Warning: This feature is primarily intended for developer testing. It is + * strongly advised not to use this authentication method in production. + */ + password?: string; + /** + * [SSO] For SSO Authentication, if `noRedirect` is set to true, it will + * open the SAML auth flow in a popup, instead of redirecting the browser in + * place. + * @deprecated This option is deprecated. + * @default false + */ + noRedirect?: boolean; + /** + * [SSO] For SSO Authentication, if `inPopup` is set to true, it will open + * the SAML auth flow in a popup, instead of redirecting the browser in place. + * + * Need to use this with `authTriggerContainer`. Or manually trigger + * the `AuthEvent.TRIGGER_SSO_POPUP` event on a user interaction. + * @version SDK: 1.18.0 + * @default false + */ + inPopup?: boolean; + /** + * [SSO] For SSO Authentication, one can supply an optional path param; + * This will be the path on the host origin where the SAML flow will be + * terminated. + * + * Eg: "/dashboard", "#/foo" [Do not include the host] + * @version SDK: 1.10.2 | ThoughtSpot: 8.2.0.cl, 8.4.1.sw + */ + redirectPath?: string; + /** @internal */ + basepath?: string; + /** + * Boolean to define if the query parameters in the ThoughtSpot URL + * should be encoded in base64. This provides additional security to + * ThoughtSpot clusters against cross-site scripting attacks. + * @default false + */ + shouldEncodeUrlQueryParams?: boolean; + /** + * Suppress cookie access alert when third-party cookies are blocked by the + * user's browser. Third-party cookie blocking is the default behaviour on + * some web browsers like Safari. If you set this attribute to `true`, + * you are encouraged to handle `noCookieAccess` event, to show your own treatment + * in this case. + * @default false + */ + suppressNoCookieAccessAlert?: boolean; + /** + * Ignore the cookie access alert when third-party cookies are blocked by the + * user's browser. If you set this to `true`, the embedded iframe behaviour + * persists even in the case of a non-logged-in user. + * @default false + */ + ignoreNoCookieAccess?: boolean; + /** + * Re-login a user with the previous login options + * when a user session expires. + * @default false + */ + autoLogin?: boolean; + /** + * Disable redirection to the login page when the embedded session expires + * This flag is typically used alongside the combination of authentication modes such + * as {@link AuthType.AuthServer} and auto-login behavior {@link + * EmbedConfig.autoLogin} + * @version SDK: 1.9.3 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + * @default false + */ + disableLoginRedirect?: boolean; + /** + * This message is displayed in the embedded view when a user login fails. + * @version SDK: 1.10.1 | ThoughtSpot: 8.2.0.cl, 8.4.1.sw + */ + loginFailedMessage?: string; + /** + * Calls the prefetch method internally when set to `true` + * @default false + */ + callPrefetch?: boolean; + /** + * When there are multiple objects embedded, queue the rendering of embedded objects + * to start after the previous embed's render is complete. This helps improve + * performance by decreasing the load on the browser. + * @version SDK: 1.5.0 | ThoughtSpot: ts7.oct.cl, 7.2.1 + * @default false + */ + queueMultiRenders?: boolean; + /** + * [AuthServer|Basic] Detect if third-party cookies are enabled by doing an + * additional call. This is slower and should be avoided. Listen to the + * `NO_COOKIE_ACCESS` event to handle the situation. + * + * This is slightly slower than letting the browser handle the cookie check, as it + * involves an extra network call. + * @version SDK: 1.10.4 | ThoughtSpot: 8.2.0.cl, 8.4.1.sw + */ + detectCookieAccessSlow?: boolean; + /** + * Hide the `beta` alert warning message for SearchEmbed. + * @version SDK: 1.12.0 | ThoughtSpot: 8.4.0.cl, 8.4.1.sw + */ + suppressSearchEmbedBetaWarning?: boolean; + /** + * Custom style params for embed Config. + * @version SDK: 1.17.0 | ThoughtSpot: 8.9.0.cl, 9.0.1.sw + */ + customizations?: CustomisationsInterface; + /** + * For `inPopup` SAMLRedirect or OIDCRedirect authentication, we need a + * button that the user can click to trigger the flow. + * This attribute sets a containing element for that button. + * @version SDK: 1.17.0 | ThoughtSpot: 8.9.0.cl, 9.0.1.sw + * @example + * ```js + * init({ + * authType: AuthType.SAMLRedirect, + * inPopup: true, + * authTriggerContainer: '#auth-trigger-container' + * }) + * ``` + */ + authTriggerContainer?: string | HTMLElement; + /** + * Specify that we want to use the `AuthEvent.TRIGGER_SSO_POPUP` event to trigger + * SAML popup. This is useful when you want to trigger the popup on a custom user + * action. + * + */ + useEventForSAMLPopup?: boolean; + /** + * Text to show in the button which triggers the popup auth flow. + * Default: `Authorize`. + * @version SDK: 1.17.0 | ThoughtSpot: 8.9.0.cl, 9.0.1.sw + */ + authTriggerText?: string; + /** + * Prevent embedded application users from accessing ThoughtSpot + * application pages outside of the iframe. + * @version SDK: 1.22.0 | ThoughtSpot: 9.3.0.cl, 9.5.1.sw + * @default true + */ + blockNonEmbedFullAppAccess?: boolean; + /** + * Host config in case embedded app is inside TS app itself + * @hidden + */ + hostConfig?: { + hostUserGuid: string; + hostClusterId: string; + hostClusterName: string; + }; + /** + * Pendo API key to enable Pendo tracking to your own subscription, the key + * is added as an additional key to the embed, as per this link:https://support.pendo.io/hc/en-us/articles/360032201951-Send-data-to-multiple-subscriptions[document]. + * @version SDK: 1.27.0 | ThoughtSpot: 9.8.0.cl + */ + pendoTrackingKey?: string; + /** + * If passed as true all alerts will be suppressed in the embedded app. + * @version SDK: 1.26.2 | ThoughtSpot: * + */ + suppressErrorAlerts?: boolean; + /** + * Suppress or show specific types of logs in the console output. + * For example, `LogLevel.ERROR` shows only Visual Embed SDK and + * ThoughtSpot application errors and suppresses + * other logs such as warnings, information alerts, + * and debug messages in the console output. + * + * @version SDK: 1.26.7 | ThoughtSpot: 9.10.0.cl + * @default LogLevel.ERROR + * @example + * ```js + * init({ + * ...embedConfig, + * logLevel: LogLevel.SILENT + * }) + * ``` + */ + logLevel?: LogLevel; + /** + * Disables the Mixpanel tracking from the SDK. + * @version SDK: 1.27.9 + * @hidden + */ + disableSDKTracking?: boolean; + /** + * Overrides default/user preferred locale for date formatting + * @version SDK: 1.28.4 | ThoughtSpot: 10.0.0.cl, 9.5.0.sw + */ + dateFormatLocale?: string; + /** + * Overrides default/user preferred locale for number formatting + * @version SDK: 1.28.4 | ThoughtSpot: 10.0.0.cl, 9.5.0.sw + */ + numberFormatLocale?: string; + /** + * Format to be used for currency when currency format is set to infer from browser + * @version SDK: 1.28.4 | ThoughtSpot: 10.0.0.cl, 9.5.0.sw + */ + currencyFormat?: string; + /** + * This flag is used to disable the token verification in the SDK. + * Enabling this flag will also disable the caching of the token. + * @hidden + * @version SDK: 1.28.5 | ThoughtSpot: 9.10.0.cl, 10.1.0.sw + * @example + * ```js + * init({ + * ...embedConfig, + * disableTokenVerification : true + * }) + * ``` + */ + disableTokenVerification?: boolean; + /** + * This flag is used to disable showing the login failure page in the embedded app. + * @version SDK: 1.32.3 | ThoughtSpot: 10.1.0.cl, 10.1.0.sw + */ + disableLoginFailurePage?: boolean; + /** + * This is an object (key/val) of override flags which will be applied + * to the internal embedded object. This can be used to add any + * URL flag. + * Warning: This option is for advanced use only and is used internally + * to control embed behavior in non-regular ways. We do not publish the + * list of supported keys and values associated with each. + * @version SDK: 1.33.5 | ThoughtSpot: * + * @example + * ```js + * const embed = new LiveboardEmbed('#embed', { + * ... // other liveboard view config + * additionalFlags: { + * flag1: 'value1', + * flag2: 'value2' + * } + * }); + * ``` + */ + additionalFlags?: { + [key: string]: string | number | boolean; + }; + /** + * This is an object (key/val) for customVariables being + * used by the third party tool's script. + * @version SDK: 1.37.0 | ThoughtSpot: 10.8.0.cl + * @example + * ```js + * const embed = new LiveboardEmbed('#embed', { + * ... // other liveboard view config + * customVariablesForThirdPartyTools: { + * key1: 'value1', + * key2: 'value2' + * } + * }); + * ``` + */ + customVariablesForThirdPartyTools?: Record; + disablePreauthCache?: boolean; + /** + * Disable fullscreen presentation mode functionality. When enabled, prevents entering + * and exiting fullscreen mode for embedded visualizations during presentations. + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + * @default true (feature is disabled by default) + * @example + * ```js + * init({ + * ... // other embed config options + * disableFullscreenPresentation: false, // enables the feature + * }) + * ``` + */ + disableFullscreenPresentation?: boolean; + /** + * Custom Actions allows users to define interactive UI actions (like buttons or menu + * items) that appear in ThoughtSpot's visualizations, answers, and Liveboards. These + * actions enable users to trigger custom workflows — such as navigating to an + * external app, calling an API, or opening a modal — based on the data context of + * what they clicked can be used to trigger custom logic when the action is clicked. + * @version SDK: 1.43.0 | ThoughtSpot: 10.14.0.cl + * @example + * ```js + * import { CustomActionsPosition, CustomActionTarget } from '@thoughtspot/visual-embed-sdk'; + * init({ + * ... // other embed config options + * customActions: [ + * { + * // Unique identifier for the custom action + * id: 'my-custom-action', + * + * // Display name shown to users in the UI + * name: 'My Custom Action', + * + * // Where the action appears in the UI + * // PRIMARY: Shows as a primary button (e.g., in the toolbar) + * // MENU: Shows in the "More" menu (three dots menu) + * // CONTEXTMENU: Shows in the right-click context menu + * position: CustomActionsPosition.PRIMARY, + * + * // What type of content this action applies to + * // ANSWER: Available on answer pages + * target: CustomActionTarget.ANSWER, + * + * // Optional: Restrict where this action appears based on data models + * // dataModelIds: { + * // // Restrict to specific data models + * // modelIds: ['model-id-1', 'model-id-2'], + * // // Restrict to specific columns within models + * // modelColumnNames: ['model-id::column-name'] + * // }, + * + * // Optional: Restrict where this action appears based on metadata + * // metadataIds: { + * // // Restrict to specific answers + * // answerIds: ['answer-id-1', 'answer-id-2'], + * // }, + * // // Restrict to specific groups (for group-based access control) + * // groupIds: ['group-id-1', 'group-id-2'], + * // // Restrict to specific organizations (for multi-org deployments) + * // orgIds: ['org-id-1', 'org-id-2'], + * } + * ], + * }) + * ``` + * @example + * ```js + * import { CustomActionsPosition, CustomActionTarget } from '@thoughtspot/visual-embed-sdk'; + * init({ + * ... // other embed config options + * customActions: [ + * { + * // Unique identifier for the custom action + * id: 'my-custom-action', + * + * // Display name shown to users in the UI + * name: 'My Custom Action', + * + * // Where the action appears in the UI + * // MENU: Shows in the "More" menu (three dots menu) + * // CONTEXTMENU: Shows in the right-click context menu + * position: CustomActionsPosition.MENU, + * + * // What type of content this action applies to + * // SPOTTER: Available in Spotter (AI-powered search) + * target: CustomActionTarget.SPOTTER, + * + * // Optional: Restrict where this action appears based on data models + * // dataModelIds: { + * // // Restrict to specific data models + * // modelIds: ['model-id-1', 'model-id-2'], + * // }, + * // // Restrict to specific groups (for group-based access control) + * // groupIds: ['group-id-1'], + * // // Restrict to specific organizations (for multi-org deployments) + * // orgIds: ['org-id-1'], + * } + * ], + * }) + * ``` + * @example + * ```js + * import { CustomActionsPosition, CustomActionTarget } from '@thoughtspot/visual-embed-sdk'; + * init({ + * ... // other embed config options + * customActions: [ + * { + * // Unique identifier for the custom action + * id: 'my-liveboard-custom-action', + * + * // Display name shown to users in the UI + * name: 'My Liveboard Custom Action', + * + * // Where the action appears in the UI + * // PRIMARY: Shows as a primary button (e.g., in the toolbar) + * // MENU: Shows in the "More" menu (three dots menu) + * position: CustomActionsPosition.PRIMARY, + * + * // What type of content this action applies to + * // LIVEBOARD: Available on liveboard pages + * target: CustomActionTarget.LIVEBOARD, + * + * // Optional: Restrict where this action appears based on metadata + * // metadataIds: { + * // // Restrict to specific liveboards + * // liveboardIds: ['liveboard-id-1', 'liveboard-id-2'], + * // }, + * // // Restrict to specific groups (for group-based access control) + * // groupIds: ['group-id-1', 'group-id-2'], + * // // Restrict to specific organizations (for multi-org deployments) + * // orgIds: ['org-id-1', 'org-id-2'], + * }, + * { + * // Unique identifier for the custom action + * id: 'my-viz-custom-action', + * + * // Display name shown to users in the UI + * name: 'My Viz Custom Action', + * + * // Where the action appears in the UI + * // PRIMARY: Shows as a primary button (e.g., in the toolbar) + * // MENU: Shows in the "More" menu (three dots menu) + * // CONTEXTMENU: Shows in the right-click context menu + * position: CustomActionsPosition.PRIMARY, + * + * // What type of content this action applies to + * // VIZ: Available on individual visualizations + * target: CustomActionTarget.VIZ, + * + * // Optional: Restrict where this action appears based on metadata + * // metadataIds: { + * // // Restrict to specific answers + * // answerIds: ['answer-id-1', 'answer-id-2'], + * // // Restrict to specific liveboard. If liveboardId is + * // // passed, custom actions will appear on all vizzes of liveboard + * // liveboardIds: ['liveboard-id-1'], + * // // Restrict to specific vizIds + * // vizIds: ['viz-id-1'] + * // }, + * // dataModelIds: { + * // // Restrict to specific data models + * // modelIds: ['model-id-1', 'model-id-2'], + * // // Restrict to specific columns within models + * // modelColumnNames: ['model-id::column-name'] + * // }, + * // // Restrict to specific groups (for group-based access control) + * // groupIds: ['group-id-1', 'group-id-2'], + * // // Restrict to specific organizations (for multi-org deployments) + * // orgIds: ['org-id-1', 'org-id-2'], + * } + * ], + * }) + * ``` + */ + customActions?: CustomAction[]; + /** + * Wait for the cleanup to be completed before destroying the embed. + * @version SDK: 1.41.0 | ThoughtSpot: 10.12.0.cl + * @default false + */ + waitForCleanupOnDestroy?: boolean; + /** + * The timeout for the cleanup to be completed before destroying the embed. + * @version SDK: 1.41.0 | ThoughtSpot: 10.12.0.cl + * @default 5000 + */ + cleanupTimeout?: number; +} +export interface LayoutConfig { +} +/** + * Embedded iframe configuration + * @group Embed components + */ +export interface FrameParams { + /** + * The width of the iframe (unit is pixels if numeric). + */ + width?: number | string; + /** + * The height of the iframe (unit is pixels if numeric). + */ + height?: number | string; + /** + * Set to 'lazy' to enable lazy loading of the embedded TS frame. + * This will defer loading of the frame until it comes into the + * viewport. This is useful for performance optimization. + */ + loading?: 'lazy' | 'eager' | 'auto'; + /** + * These parameters will be passed to the iframe + * as is. + */ + [key: string]: string | number | boolean | undefined; +} +/** + * The common configuration object for an embedded view. + */ +export interface BaseViewConfig extends ApiInterceptFlags { + /** + * @hidden + */ + layoutConfig?: LayoutConfig; + /** + * The width and height dimensions to render an embedded + * object inside your app. Specify the values in pixels or percentage. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed`, `SearchEmbed`, `SpotterAgentEmbed`, `SpotterEmbed`, `SearchBarEmbed` + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 7.2.1 + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed, SearchEmbed, or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * frameParams: { + * width: '500px' | '50%', + * height: '400px' | '60%', + * }, + * }) + * ``` + */ + frameParams?: FrameParams; + /** + * @hidden + */ + theme?: string; + /** + * @hidden + */ + styleSheet__unstable?: string; + /** + * The list of actions to disable from the primary menu, more menu + * (...), and the contextual menu. Disabled actions are grayed out + * and still visible to the user, but cannot be clicked. + * Use this when you want to disable an action (keep it visible but non-interactive). + * To completely remove an action from the UI, use {@link hiddenActions} instead. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed`, `SearchEmbed`, `SpotterAgentEmbed`, `SpotterEmbed`, `SearchBarEmbed` + * @version SDK: 1.6.0 | ThoughtSpot: ts8.nov.cl, 8.4.1.sw + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed, SearchEmbed, or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * disabledActions: [Action.Download, Action.Save], + * }); + * ``` + */ + disabledActions?: Action[]; + /** + * The tooltip to display for disabled actions. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed`, `SearchEmbed`, `SpotterAgentEmbed`, `SpotterEmbed`, `SearchBarEmbed` + * @version SDK: 1.6.0 | ThoughtSpot: ts8.nov.cl, 8.4.1.sw + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed, SearchEmbed, or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * disabledActions: [Action.Download, Action.Save], + * disabledActionReason: "Reason for disabling", + * }); + * ``` + */ + disabledActionReason?: string; + /** + * The list of actions to completely remove from the embedded view. + * Hidden actions are not visible to the user at all (fully removed from the UI). + * Use this when you want to remove an action entirely. + * To keep an action visible but non-interactive (grayed out), use {@link disabledActions} instead. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed`, `SearchEmbed`, `SpotterAgentEmbed`, `SpotterEmbed`, `SearchBarEmbed` + * @version SDK: 1.6.0 | ThoughtSpot: ts8.nov.cl, 8.4.1.sw + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed, SearchEmbed, or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * hiddenActions: [Action.Download, Action.Export], + * }); + * ``` + * @important + */ + hiddenActions?: Action[]; + /** + * The list of actions to display from the primary menu, more menu + * (...), and the contextual menu. These will be only actions that + * are visible to the user. + * Use this as an allowlist — only the actions listed here will be shown. + * All other actions will be hidden. Use either this or {@link hiddenActions}, not both. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed`, `SearchEmbed`, `SpotterAgentEmbed`, `SpotterEmbed`, `SearchBarEmbed` + * @version SDK: 1.6.0 | ThoughtSpot: ts8.nov.cl, 8.4.1.sw + * @important + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed, SearchEmbed, or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * visibleActions: [Action.Download, Action.Export], + * }); + * ``` + */ + visibleActions?: Action[]; + /** + * The locale settings to apply to the embedded view. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed`, `SearchEmbed`, `SpotterAgentEmbed`, `SpotterEmbed`, `SearchBarEmbed` + * @version SDK: 1.9.4 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed, SearchEmbed, or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * locale:'en', + * }) + * ``` + */ + locale?: string; + /** + * This is an object (key/val) of override flags which will be applied + * to the internal embedded object. This can be used to add any + * URL flag. + * If the same flags are passed in init, they will be overridden by the values here. + * Warning: This option is for advanced use only and is used internally + * to control embed behavior in non-regular ways. We do not publish the + * list of supported keys and values associated with each. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed`, `SearchEmbed`, `SpotterAgentEmbed`, `SpotterEmbed`, `SearchBarEmbed` + * @version SDK: 1.9.0 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed, SearchEmbed, or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * additionalFlags: { + * flag1: 'value1', + * flag2: 'value2' + * }, + * }); + * ``` + */ + additionalFlags?: { + [key: string]: string | number | boolean; + }; + /** + * Dynamic CSSUrl and customCSS to be injected in the loaded application. + * You would also need to set `style-src` in the CSP settings. + * @version SDK: 1.17.2 | ThoughtSpot: 8.4.1.sw, 8.4.0.cl + * @default '' + */ + customizations?: CustomisationsInterface; + /** + * Insert as a sibling of the target container, instead of appending to a + * child inside it. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed`, `SearchEmbed`, `SpotterAgentEmbed`, `SpotterEmbed`, `SearchBarEmbed` + * @version SDK: 1.2.0 | ThoughtSpot: 9.0.0.cl, 9.0.0.sw + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed, SearchEmbed, or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * insertAsSibling:true, + * }) + * ``` + */ + insertAsSibling?: boolean; + /** + * Use a pre-rendered iframe from a pool of pre-rendered iframes + * if available and matches the configuration. + * @version SDK: 1.22.0 + * @hidden + * + * See [docs]() on how to create a prerender pool. + */ + usePrerenderedIfAvailable?: boolean; + /** + * PreRender id to be used for PreRendering the embed. + * Use PreRender to render the embed in the background and then + * show or hide the rendered embed using showPreRender or hidePreRender respectively. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed`, `SearchEmbed`, `SpotterAgentEmbed`, `SpotterEmbed`, `SearchBarEmbed` + * @version SDK: 1.25.0 | ThoughtSpot: 9.6.0.cl, 9.8.0.sw + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed, SearchEmbed, or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * preRenderId: "preRenderId-123", + * }); + * embed.showPreRender(); + * ``` + */ + preRenderId?: string; + /** + * Determines if the PreRender component should dynamically track the size + * of its embedding element and adjust its own size accordingly. + * Enabling this option allows the PreRender component to automatically adapt + * its dimensions based on changes to the size of the embedding element. + * @type {boolean} + * @default false + * @version SDK: 1.24.0 | ThoughtSpot: 9.4.0.cl, 9.4.0.sw + * @example + * ```js + * // Disable tracking PreRender size in the configuration + * const config = { + * doNotTrackPreRenderSize: true, + * }; + * + * // Instantiate an object with the configuration + * const myComponent = new MyComponent(config); + * ``` + */ + doNotTrackPreRenderSize?: boolean; + /** + * Enable the V2 shell. This can provide performance benefits + * due to a lighter-weight shell. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed`, `SearchEmbed`, `SpotterAgentEmbed`, `SpotterEmbed`, `SearchBarEmbed` + * @version SDK: 1.31.2 | ThoughtSpot: 10.0.0.cl + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed, SearchEmbed, or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * enableV2Shell_experimental: true, + * }); + * ``` + */ + enableV2Shell_experimental?: boolean; + /** + * For internal tracking of the embed component type. + * @hidden + */ + embedComponentType?: string; + /** + * This flag can be used to expose translation IDs on the embedded app. + * @default false + * @version SDK: 1.37.0 | ThoughtSpot: 10.9.0.cl + */ + exposeTranslationIDs?: boolean; + /** + * This flag can be used to disable links inside the embedded app, + * and disable redirection of links in a new tab. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed`, `SearchEmbed`, `SpotterAgentEmbed`, `SpotterEmbed`, `SearchBarEmbed` + * @version SDK: 1.32.1 | ThoughtSpot: 10.3.0.cl + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed, SearchEmbed, or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * disableRedirectionLinksInNewTab: true, + * }); + * ``` + */ + disableRedirectionLinksInNewTab?: boolean; + /** + * Overrides an Org context for embedding application users. + * This parameter allows a user authenticated to one Org to view the + * objects from another Org. + * The `overrideOrgId` setting is honoured only if the + * Per Org URL feature is enabled on your ThoughtSpot instance. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed`, `SearchEmbed`, `SpotterAgentEmbed`, `SpotterEmbed`, `SearchBarEmbed` + * @version SDK: 1.35.0 | ThoughtSpot: 10.5.0.cl + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed, SearchEmbed, or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * overrideOrgId: 142536, + * }); + * ``` + */ + overrideOrgId?: number; + /** + * Flag to override the *Open Link in New Tab* context + * menu option. + * + * For improved link override handling, use + * {@link enableLinkOverridesV2} instead. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed`, + * `SearchEmbed`, `SpotterAgentEmbed`, + * `SpotterEmbed`, `SearchBarEmbed` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... // other embed view config + * linkOverride: true, + * }) + * ``` + */ + linkOverride?: boolean; + /** + * Enables the V2 link override mechanism with improved + * handling. When enabled, navigation links within the + * embedded ThoughtSpot app are intercepted and routed + * through the SDK via the `EmbedEvent.DialogOpen` + * event. + * + * The SDK automatically sends {@link linkOverride} + * alongside this flag for backward compatibility with + * older ThoughtSpot versions. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed`, + * `SearchEmbed`, `SpotterAgentEmbed`, + * `SpotterEmbed`, `SearchBarEmbed` + * @version SDK: 1.46.0 | ThoughtSpot: 26.2.0.cl + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... // other embed view config + * enableLinkOverridesV2: true, + * }); + * + * embed.on(EmbedEvent.DialogOpen, (payload) => { + * console.log('Link clicked:', payload); + * }); + * ``` + */ + enableLinkOverridesV2?: boolean; + /** + * The primary action to display on top of the viz for Liveboard and App Embed. + * Use this to set the primary action. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @version SDK: 1.39.0 | ThoughtSpot: 10.11.0.cl + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * primaryAction: Action.Download + * }); + * ``` + */ + primaryAction?: Action | string; + /** + * flag to enable insert into slides action + * @hidden + * @private + */ + insertInToSlide?: boolean; + /** + * Show alert messages and toast messages in the embed. + * Supported in all embed types. + * + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... // other embed view config + * showAlerts:true, + * }) + * ``` + */ + showAlerts?: boolean; + /** + * Custom Actions allows users to define interactive UI actions (like buttons or menu + * items) that appear in ThoughtSpot's visualizations, answers, and Liveboards. These + * actions enable users to trigger custom workflows — such as navigating to an + * external app, calling an API, or opening a modal — based on the data context of + * what they clicked can be used to trigger custom logic when the action is clicked. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed`, `SearchEmbed`, `SpotterEmbed` + * @version SDK: 1.43.0 | ThoughtSpot: 10.14.0.cl + * @example + * ```ts + * import { + * CustomActionPayload, + * CustomActionsPosition, + * CustomActionTarget, + * } from '@thoughtspot/visual-embed-sdk'; + * // Use supported embed types such as AppEmbed or LiveboardEmbed + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... // other embed config options + * customActions: [ + * { + * // Unique identifier for the custom action + * id: 'my-custom-action', + * + * // Display name shown to users in the UI + * name: 'My Custom Action', + * + * // Where the action appears in the UI + * // PRIMARY: Shows as a primary button (e.g., in the toolbar) + * // MENU: Shows in the "More" menu (three dots menu) + * // CONTEXTMENU: Shows in the right-click context menu + * position: CustomActionsPosition.PRIMARY, + * + * // What type of content this action applies to + * // ANSWER: Available on answer pages + * target: CustomActionTarget.ANSWER, + * + * // Optional: Restrict where this action appears based on data models + * // dataModelIds: { + * // // Restrict to specific data models + * // modelIds: ['model-id-1', 'model-id-2'], + * // // Restrict to specific columns within models + * // modelColumnNames: ['model-id::column-name'] + * // }, + * + * // Optional: Restrict where this action appears based on metadata + * // metadataIds: { + * // // Restrict to specific answers + * // answerIds: ['answer-id-1', 'answer-id-2'], + * // }, + * // // Restrict to specific groups (for group-based access control) + * // groupIds: ['group-id-1', 'group-id-2'], + * // // Restrict to specific organizations (for multi-org deployments) + * // orgIds: ['org-id-1', 'org-id-2'], + * } + * ], + * }) + * + * // to trigger a custom flow on custom action click listen to Custom action embed event + * embed.on(EmbedEvent.CustomAction, (payload: CustomActionPayload) => { + * console.log('Custom Action event:', payload); + * }) + * ``` + * @example + * ```ts + * import { + * CustomActionsPosition, + * CustomActionTarget, + * } from '@thoughtspot/visual-embed-sdk'; + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... // other embed config options + * customActions: [ + * { + * // Unique identifier for the custom action + * id: 'my-custom-action', + * + * // Display name shown to users in the UI + * name: 'My Custom Action', + * + * // Where the action appears in the UI + * // MENU: Shows in the "More" menu (three dots menu) + * // CONTEXTMENU: Shows in the right-click context menu + * position: CustomActionsPosition.MENU, + * + * // What type of content this action applies to + * // SPOTTER: Available in Spotter (AI-powered search) + * target: CustomActionTarget.SPOTTER, + * + * // Optional: Restrict where this action appears based on data models + * // dataModelIds: { + * // // Restrict to specific data models + * // modelIds: ['model-id-1', 'model-id-2'], + * // }, + * // // Restrict to specific groups (for group-based access control) + * // groupIds: ['group-id-1'], + * // // Restrict to specific organizations (for multi-org deployments) + * // orgIds: ['org-id-1'], + * } + * ], + * }) + * ``` + * @example + * ```ts + * import { + * CustomActionsPosition, + * CustomActionTarget, + * } from '@thoughtspot/visual-embed-sdk'; + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... // other embed config options + * customActions: [ + * { + * // Unique identifier for the custom action + * id: 'my-liveboard-custom-action', + * + * // Display name shown to users in the UI + * name: 'My Liveboard Custom Action', + * + * // Where the action appears in the UI + * // PRIMARY: Shows as a primary button (e.g., in the toolbar) + * // MENU: Shows in the "More" menu (three dots menu) + * position: CustomActionsPosition.PRIMARY, + * + * // What type of content this action applies to + * // LIVEBOARD: Available on liveboard pages + * target: CustomActionTarget.LIVEBOARD, + * + * // Optional: Restrict where this action appears based on metadata + * // metadataIds: { + * // // Restrict to specific liveboards + * // liveboardIds: ['liveboard-id-1', 'liveboard-id-2'], + * // }, + * // // Restrict to specific groups (for group-based access control) + * // groupIds: ['group-id-1', 'group-id-2'], + * // // Restrict to specific organizations (for multi-org deployments) + * // orgIds: ['org-id-1', 'org-id-2'], + * }, + * { + * // Unique identifier for the custom action + * id: 'my-viz-custom-action', + * + * // Display name shown to users in the UI + * name: 'My Viz Custom Action', + * + * // Where the action appears in the UI + * // PRIMARY: Shows as a primary button (e.g., in the toolbar) + * // MENU: Shows in the "More" menu (three dots menu) + * // CONTEXTMENU: Shows in the right-click context menu + * position: CustomActionsPosition.PRIMARY, + * + * // What type of content this action applies to + * // VIZ: Available on individual visualizations + * target: CustomActionTarget.VIZ, + * + * // Optional: Restrict where this action appears based on metadata + * // metadataIds: { + * // // Restrict to specific answers + * // answerIds: ['answer-id-1', 'answer-id-2'], + * // // Restrict to specific liveboard. If liveboardId is + * // // passed, custom actions will appear on all vizzes of liveboard + * // liveboardIds: ['liveboard-id-1'], + * // // Restrict to specific vizIds + * // vizIds: ['viz-id-1'] + * // }, + * // dataModelIds: { + * // // Restrict to specific data models + * // modelIds: ['model-id-1', 'model-id-2'], + * // // Restrict to specific columns within models + * // modelColumnNames: ['model-id::column-name'] + * // }, + * // // Restrict to specific groups (for group-based access control) + * // groupIds: ['group-id-1', 'group-id-2'], + * // // Restrict to specific organizations (for multi-org deployments) + * // orgIds: ['org-id-1', 'org-id-2'], + * } + * ], + * }) + * ``` + */ + customActions?: CustomAction[]; + /** + * Refresh the auth token when the token is near expiry. + * @version SDK: 1.45.2 | ThoughtSpot: 26.3.0.cl + * @default true + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... // other embed view config + * refreshAuthTokenOnNearExpiry: true, + * }) + * ``` + */ + refreshAuthTokenOnNearExpiry?: boolean; + /** + * This flag skips payload validation so events can be processed even if the payload is old, incomplete, or from a trusted system. + * @default false + * @version SDK: 1.45.2 | ThoughtSpot: 26.3.0.cl + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... // other embed view config + * shouldBypassPayloadValidation:true, + * }) + * ``` + */ + shouldBypassPayloadValidation?: boolean; + /** + * Flag to use host events v2. This is used to enable the new host events v2 API. + * @default false + * @version SDK: 1.45.2 | ThoughtSpot: 26.3.0.cl + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * ... // other embed view config + * useHostEventsV2:true, + * }) + * ``` + */ + useHostEventsV2?: boolean; +} +/** + * Configuration for {@link startAutoMCPFrameRenderer}. + * + * Extends {@link BaseViewConfig} but omits params that are not applicable + * to the auto-frame renderer: + * - `preRenderId` / `usePrerenderedIfAvailable` / `doNotTrackPreRenderSize` — + * the renderer always replaces a live iframe in-place; prerender pools are not used. + * - `insertAsSibling` — insertion is always a same-position `replaceWith`; the + * container-append path is never taken. + * - `primaryAction` — a Liveboard/AppEmbed-specific feature; the renderer renders + * whatever route the MCP server specifies. + * - `enableV2Shell_experimental` — the renderer always uses the v2 URL format; + * this flag has no effect. + */ +export type AutoMCPFrameRendererViewConfig = Omit; +/** + * The configuration object for Home page embeds configs. + */ +export interface HomePageConfig { + /** + * Hide columns on list pages such as + * *Liveboards* and *Answers*. + * For example: `hiddenListColumns = [ListPageColumns.Author]` + * + * **Note**: This option is available only in full app embedding and requires + * importing the `ListPageColumns` enum. Starting with version 10.14.0.cl, you can + * use this attribute to + * hide the columns on all list pages in the *Insights* section. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.38.0 | ThoughtSpot: 10.9.0.cl + * @example + * ```js + * import { ListPageColumns } from '@thoughtspot/visual-embed-sdk'; + * + * const embed = new AppEmbed('#tsEmbed', { + * ... //other embed view config + * hiddenListColumns : [ListPageColumns.Favorite,ListPageColumns.Author], + * }) + * ``` + */ + hiddenListColumns?: ListPageColumns[]; + /** + * Control the visibility of home page modules. + * To specify the modules, import the `HomepageModule` enum. + * For example: `hiddenHomepageModules = [HomepageModule.MyLibrary]` + * + * **Note**: This attribute is not supported in the classic (v1) experience. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl, 10.1.0.sw + * @example + * ```js + * import { HomepageModule } from '@thoughtspot/visual-embed-sdk'; + * + * const embed = new AppEmbed('#tsEmbed', { + * ... // V2/V3 navigation and home page experience attributes + * hiddenHomepageModules : [HomepageModule.Favorite,HomepageModule.Learning], + * //...other embed view configuration attributes + * }) + * ``` + */ + hiddenHomepageModules?: HomepageModule[]; + /** + * Reorder home page modules. + * To specify the modules, import the `HomepageModule` enum. + * For example: `reorderedHomepageModules = [HomepageModule.MyLibrary, + * HomepageModule.Watchlist]` **Note**: This attribute is not supported in the + * classic (v1) homepage. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl, 10.1.0.sw + * @example + * ```js + * import { HomepageModule } from '@thoughtspot/visual-embed-sdk'; + * + * const embed = new AppEmbed('#tsEmbed', { + * ...//V2/V3 navigation and home page experience attributes + * reorderedHomepageModules:[HomepageModule.Favorite,HomepageModule.MyLibrary], + * //... other embed view configuration attributes + * }) + * ``` + */ + reorderedHomepageModules?: HomepageModule[]; + /** + * Controls the visibility of the menu items + * on the home page left navigation panel. + * To specify the menu items, import the `HomeLeftNavItem` enum. + * + * **Note**: This attribute is not supported in the classic (v1) homepage. + * + * Supported embed types: `AppEmbed` + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl, 10.1.0.sw + * @example + * ```js + * import { HomeLeftNavItem } from '@thoughtspot/visual-embed-sdk'; + * + * const embed = new AppEmbed('#tsEmbed', { + * //... V2/V3 experience attributes + * hiddenHomeLeftNavItems : [HomeLeftNavItem.Home,HomeLeftNavItem.Answers], + * ... //other embed view configuration attributes + * }) + * ``` + */ + hiddenHomeLeftNavItems?: HomeLeftNavItem[]; +} +/** + * The configuration object for common Search and Liveboard embeds configs. + */ +export interface SearchLiveboardCommonViewConfig { + /** + * The list of runtime filters to apply to a search Answer, + * visualization, or Liveboard. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed`, `SearchEmbed` + * @version SDK: 1.9.4 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed, SearchEmbed, or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * runtimeFilters: [ + * { + * columnName: 'value', + * operator: RuntimeFilterOp.EQ, + * values: ['string' | 123 | true], + * }, + * ], + * }) + * ``` + */ + runtimeFilters?: RuntimeFilter[]; + /** + * The list of parameter overrides to apply to a search Answer, + * visualization, or Liveboard. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed`, `SearchEmbed` + * @version SDK: 1.25.0 | ThoughtSpot: 9.2.0.cl, 9.5.0.sw + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed, SearchEmbed, or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * runtimeParameters: [ + * { + * name: 'value', + * value: 'string' | 123 | true, + * }, + * ] + * }) + * ``` + */ + runtimeParameters?: RuntimeParameter[]; + /** + * flag to set ContextMenu Trigger to either left or right click. + * + * Supported embed types: `AppEmbed`, `SearchEmbed` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed, or SearchEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * contextMenuTrigger:ContextMenuTriggerOptions.LEFT_CLICK || RIGHT_CLICK, + * }) + * ``` + */ + contextMenuTrigger?: ContextMenuTriggerOptions; + /** + * Boolean to exclude runtimeFilters in the URL + * By default it is true, this flag removes runtime filters from the URL + * (default behavior from SDK 1.45.0). + * when set to false, runtime filters will be included in the URL + * (default behavior before SDK 1.45.0). + * + * Irrespective of this flag, runtime filters ( if passed ) will be applied to the + * embedded view. + * @default true + * @version SDK: 1.24.0 | ThoughtSpot: 9.5.0.cl + */ + excludeRuntimeFiltersfromURL?: boolean; + /** + * Boolean to exclude runtimeParameters from the URL + * when set to true, this flag removes runtime parameters from the URL + * (default behavior from SDK 1.45.0). + * when set to false, runtime parameters will be included in the URL + * (default behavior before SDK 1.45.0). + * + * Irrespective of this flag, runtime parameters (if passed) will be applied to the + * embedded view. + * @default true + * @version SDK: 1.29.0 | ThoughtSpot: 10.1.0.cl + */ + excludeRuntimeParametersfromURL?: boolean; + /** + * To set the initial state of the search bar in case of saved Answers. + * + * Supported embed types: `AppEmbed`, `SearchBarEmbed` + * @default true + * @version SDK: 1.34.0 | ThoughtSpot: 10.3.0.cl + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed, or SearchBarEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * collapseSearchBar: true, + * }); + * ``` + */ + collapseSearchBar?: boolean; + /** + * Flag to control Data panel experience + * + * Supported embed types: `AppEmbed`, `SearchBarEmbed`, `LiveboardEmbed`, `SearchEmbed` + * @deprecated from SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + * @default true + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed, or SearchBarEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * dataPanelV2: true, + * }) + * ``` + */ + dataPanelV2?: boolean; + /** + * To enable custom column groups in data panel v2 + * + * Supported embed types: `SearchBarEmbed`, `LiveboardEmbed`, `SearchEmbed` + * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl, 10.1.0.sw + * @default false + * @example + * ```js + * // Replace with embed component name. For example, SearchBarEmbed, or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * enableCustomColumnGroups: true, + * }); + * ``` + */ + enableCustomColumnGroups?: boolean; + /** + * To enable **Include current period** checkbox for date filters. + * Controls the visibility of the option to include + * the current time period in filter results. + * + * Supported embed types: `AppEmbed`, `SearchBarEmbed`, `LiveboardEmbed`, `SearchEmbed` + * @version SDK: 1.45.0 | ThoughtSpot: 26.4.0.cl + * @example + * ```js + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * isThisPeriodInDateFiltersEnabled: true, + * }) + * ``` + */ + isThisPeriodInDateFiltersEnabled?: boolean; +} +/** + * The configuration object for common Liveboard and App embeds configs. + */ +export interface LiveboardAppEmbedViewConfig { + /** + * Show or hide Liveboard header + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl + * @default false + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * hideLiveboardHeader : true, + * }) + * ``` + */ + hideLiveboardHeader?: boolean; + /** + * Show or hide Liveboard title + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl + * @default false + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * showLiveboardTitle:true, + * }) + * ``` + */ + showLiveboardTitle?: boolean; + /** + * Show or hide Liveboard description + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl + * @default false + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * showLiveboardDescription:true, + * }) + * ``` + */ + showLiveboardDescription?: boolean; + /** + * Boolean to control if Liveboard header is sticky or not. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#embed', { + * ... // other app view config + * isLiveboardHeaderSticky: true, + * }); + * ``` + */ + isLiveboardHeaderSticky?: boolean; + /** + * This attribute can be used to enable the two-column layout on an embedded Liveboard + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @type {boolean} + * @version SDK: 1.32.0 | ThoughtSpot: 10.1.0.cl + * @default false + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * enable2ColumnLayout: true, + * }) + * ``` + */ + enable2ColumnLayout?: boolean; + /** + * This flag can be used to enable the compact header in Liveboard + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @type {boolean} + * @version SDK: 1.35.0 | ThoughtSpot: 10.3.0.cl + * @default false + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * isLiveboardCompactHeaderEnabled: true, + * }) + * ``` + */ + isLiveboardCompactHeaderEnabled?: boolean; + /** + * This flag can be used to show or hide the Liveboard verified icon in the compact + * header. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @version SDK: 1.35.0 | ThoughtSpot: 10.4.0.cl + * @default true + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * showLiveboardVerifiedBadge: true, + * }) + * ``` + */ + showLiveboardVerifiedBadge?: boolean; + /** + * This flag is used to enable/disable hide irrelevant filters in Liveboard tab + * + * **Note**: This feature is supported only if compact header is enabled on your + * Liveboard. To enable compact header, use the `isLiveboardCompactHeaderEnabled` + * attribute. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @version SDK: 1.36.0 | ThoughtSpot: 10.6.0.cl + * @default false + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * hideIrrelevantChipsInLiveboardTabs: true, + * isLiveboardCompactHeaderEnabled: true, + * }) + * ``` + */ + hideIrrelevantChipsInLiveboardTabs?: boolean; + /** + * This flag can be used to show or hide the re-verify banner on the Liveboard + * compact header + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @version SDK: 1.35.0 | ThoughtSpot: 10.4.0.cl + * @default true + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * showLiveboardReverifyBanner: true, + * }) + * ``` + */ + showLiveboardReverifyBanner?: boolean; + /** + * enable or disable ask sage + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @version SDK: 1.29.0 | ThoughtSpot: 9.12.0.cl + * @default false + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed, SpotterEmbed, or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * enableAskSage:true, + * }) + * ``` + */ + enableAskSage?: boolean; + /** + * This flag is used to show or hide checkboxes for including or excluding + * the cover and filters pages in the Liveboard PDF. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @version SDK: 1.40.0 | ThoughtSpot: 10.8.0.cl + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * coverAndFilterOptionInPDF: false, + * }) + * ``` + */ + coverAndFilterOptionInPDF?: boolean; + /** + * This flag is used to enable or disable the new centralized Liveboard filter UX + * (v2). When enabled, a unified modal is used to manage and update multiple filters + * at once, replacing the older individual filter interactions. + * To enable this feature on your instance, contact ThoughtSpot Support. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @version SDK: 1.46.0 | ThoughtSpot: 26.4.0.cl + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * isCentralizedLiveboardFilterUXEnabled: true, + * }) + * ``` + */ + isCentralizedLiveboardFilterUXEnabled?: boolean; + /** + * This flag is used to enable or disable the link parameters in liveboard. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @version SDK: 1.42.0 | ThoughtSpot: 10.14.0.cl + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * isLinkParametersEnabled: true, + * }) + * ``` + */ + isLinkParametersEnabled?: boolean; + /** + * This flag is used to enable or disable the enhanced filter interactivity in + * liveboard. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @version SDK: 1.42.0 | ThoughtSpot: 10.15.0.cl + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * isEnhancedFilterInteractivityEnabled: true, + * }) + * ``` + */ + isEnhancedFilterInteractivityEnabled?: boolean; + /** + * Show or hide masked filter chips + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @version SDK: 1.45.0 | ThoughtSpot: 26.2.0.cl + * @default false + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * showMaskedFilterChip: true, + * }) + * ``` + */ + showMaskedFilterChip?: boolean; + /** + * Enable or disable Liveboard styling and grouping + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @version SDK: 1.45.0 | ThoughtSpot: 26.2.0.cl + * @default false + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * isLiveboardMasterpiecesEnabled: true, + * }) + * ``` + */ + isLiveboardMasterpiecesEnabled?: boolean; + /** + * Enable or disable Muze chart phase 1 GA + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed` + * @version SDK: 1.49.0 | ThoughtSpot Cloud: 26.6.0.cl + * @default false + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * newChartsLibrary: true, + * }) + * ``` + */ + newChartsLibrary?: boolean; +} +export interface AllEmbedViewConfig extends BaseViewConfig, SearchLiveboardCommonViewConfig, HomePageConfig, LiveboardAppEmbedViewConfig { +} +/** + * MessagePayload: Embed event payload: message type, data and status (start/end) + * @group Events + */ +export type MessagePayload = { + type: string; + data: any; + status?: string; +}; +/** + * MessageOptions: By providing options, getting specific event start / end based on + * option + * @group Events + */ +export type MessageOptions = { + /** + * A boolean value indicating that start status events of this type + * will be dispatched. + */ + start?: boolean; +}; +/** + * MessageCallback: Embed event message callback + * @group Events + */ +export type MessageCallback = (payload: MessagePayload, +/** + * responder: Message callback function triggered when embed event + * initiated + */ +responder?: (data: any) => void) => void; +/** + * MessageCallbackObj: contains message options & callback function + */ +export type MessageCallbackObj = { + /** + * options: It contains start, a boolean value indicating that start + * status events of this type will be dispatched + */ + options: MessageOptions; + callback: MessageCallback; +}; +export type GenericCallbackFn = (...args: any[]) => any; +export type QueryParams = { + [key: string]: string | boolean | number; +}; +/** + * A map of the supported runtime filter operations + */ +export declare enum RuntimeFilterOp { + /** + * Equals + */ + EQ = "EQ", + /** + * Does not equal + */ + NE = "NE", + /** + * Less than + */ + LT = "LT", + /** + * Less than or equal to + */ + LE = "LE", + /** + * Greater than + */ + GT = "GT", + /** + * Greater than or equal to + */ + GE = "GE", + /** + * Contains + */ + CONTAINS = "CONTAINS", + /** + * Begins with + */ + BEGINS_WITH = "BEGINS_WITH", + /** + * Ends with + */ + ENDS_WITH = "ENDS_WITH", + /** + * Between, inclusive of higher value + */ + BW_INC_MAX = "BW_INC_MAX", + /** + * Between, inclusive of lower value + */ + BW_INC_MIN = "BW_INC_MIN", + /** + * Between, inclusive of both higher and lower value + */ + BW_INC = "BW_INC", + /** + * Between, non-inclusive + */ + BW = "BW", + /** + * Is included in this list of values + */ + IN = "IN", + /** + * Is not included in this list of values + */ + NOT_IN = "NOT_IN" +} +/** + * Home page modules that can be hidden + * via `hiddenHomepageModules` and reordered via + * `reorderedHomepageModules`. + * + * **Note**: This option is not supported in the classic (v1) experience. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl, 10.1.0.sw + */ +export declare enum HomepageModule { + /** + * Search bar + */ + Search = "SEARCH", + /** + * KPI watchlist module + */ + Watchlist = "WATCHLIST", + /** + * Favorite module + */ + Favorite = "FAVORITE", + /** + * List of answers and Liveboards + */ + MyLibrary = "MY_LIBRARY", + /** + * Trending list + */ + Trending = "TRENDING", + /** + * Learning videos + */ + Learning = "LEARNING" +} +/** + * List page columns that can be hidden. + * **Note**: This option is applicable to full app embedding only. + * @version SDK: 1.38.0 | ThoughtSpot: 10.9.0.cl + */ +export declare enum ListPageColumns { + /** + * Favorites + */ + Favorites = "FAVOURITE", + /** + * Favourite Use {@link ListPageColumns.Favorites} instead. + * @deprecated This option is deprecated. + */ + Favourite = "FAVOURITE", + /** + * Tags + */ + Tags = "TAGS", + /** + * Author + */ + Author = "AUTHOR", + /** + * Last viewed/Last modified + */ + DateSort = "DATE_SORT", + /** + * Share + */ + Share = "SHARE", + /** + * Verified badge/column + */ + Verified = "VERIFIED" +} +/** + * A filter that can be applied to ThoughtSpot answers, Liveboards, or + * visualizations at runtime. + */ +export interface RuntimeFilter { + /** + * The name of the column to filter on (case-sensitive) + */ + columnName: string; + /** + * The operator to apply + */ + operator: RuntimeFilterOp; + /** + * The list of operands. Some operators like EQ, LE accept + * a single operand, whereas other operators like BW and IN accept multiple + * operands. + */ + values: (number | boolean | string | bigint)[]; +} +/** + * A filter that can be applied to ThoughtSpot Answers, Liveboards, or + * visualizations at runtime. + */ +export interface RuntimeParameter { + /** + * The name of the runtime parameter to filter on (case-sensitive) + */ + name: string; + /** + * Values + */ + value: number | boolean | string; +} +/** + * Event types emitted by the embedded ThoughtSpot application. + * + * To add an event listener use the corresponding + * {@link LiveboardEmbed.on} or {@link AppEmbed.on} or {@link SearchEmbed.on} method. + * @example + * ```js + * import { EmbedEvent } from '@thoughtspot/visual-embed-sdk'; + * // Or + * // const { EmbedEvent } = window.tsembed; + * + * // create the liveboard embed. + * + * liveboardEmbed.on(EmbedEvent.Drilldown, (drilldown) => { + * console.log('Drilldown event', drilldown); + * })); + * ``` + * + * If you are using React components for embedding, you can register to any + * events from the `EmbedEvent` list by using the `on` convention. + * For example,`onAlert`, `onCopyToClipboard` and so on. + * @example + * ```js + * // ... + * const MyComponent = ({ dataSources }) => { + * const onLoad = () => { + * console.log(EmbedEvent.Load, {}); + * }; + * + * return ( + * + * ); + * }; + * ``` + * @group Events + */ +export declare enum EmbedEvent { + /** + * Rendering has initialized. + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Init, showLoader) + * //show a loader + * function showLoader() { + * document.getElementById("loader"); + * } + * ``` + * @returns timestamp - The timestamp when the event was generated. + */ + Init = "init", + /** + * Authentication has either succeeded or failed. + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.AuthInit, payload => { + * console.log('AuthInit', payload); + * }) + * ``` + * @returns isLoggedIn - A Boolean specifying whether authentication was successful. + */ + AuthInit = "authInit", + /** + * The embed object container has loaded. + * @returns timestamp - The timestamp when the event was generated. + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Load, hideLoader) + * //hide loader + * function hideLoader() { + * document.getElementById("loader"); + * } + * ``` + */ + Load = "load", + /** + * Data pertaining to an Answer, Liveboard or Spotter visualization is received. + * The event payload includes the raw data of the object. + * @return data - Answer of Liveboard data + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Data, payload => { + * console.log('data', payload); + * }) + * ``` + * @important + */ + Data = "data", + /** + * Search query has been updated by the user. + * @version SDK: 1.4.0 | ThoughtSpot: ts7.sep.cl, 8.4.1.sw + * @example + * ```js + * searchEmbed.on(EmbedEvent.QueryChanged, payload => console.log('data', payload)) + * ``` + */ + QueryChanged = "queryChanged", + /** + * A drill-down operation has been performed. + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @returns additionalFilters - Any additional filters applied + * @returns drillDownColumns - The columns on which drill down was performed + * @returns nonFilteredColumns - The columns that were not filtered + * @example + * ```js + * searchEmbed.on(EmbedEvent.DrillDown, { + * points: { + * clickedPoint, + * selectedPoints: selectedPoint + * }, + * autoDrillDown: true, + * }) + * ``` + * In this example, `VizPointDoubleClick` event is used for + * triggering the `DrillDown` event when an area or specific + * data point on a table or chart is double-clicked. + * @example + * ```js + * searchEmbed.on(EmbedEvent.VizPointDoubleClick, (payload) => { + * console.log(payload); + * const clickedPoint = payload.data.clickedPoint; + * const selectedPoint = payload.data.selectedPoints; + * console.log('>>> called', clickedPoint); + * embed.trigger(HostEvent.DrillDown, { + * points: { + * clickedPoint, + * selectedPoints: selectedPoint + * }, + * autoDrillDown: true, + * }) + * }) + * ``` + */ + Drilldown = "drillDown", + /** + * One or more data sources have been selected. + * @returns dataSourceIds - the list of data sources + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * searchEmbed.on(EmbedEvent.DataSourceSelected, payload => { + * console.log('DataSourceSelected', payload); + * }) + * ``` + */ + DataSourceSelected = "dataSourceSelected", + /** + * One or more data columns have been selected. + * @returns columnIds - the list of columns + * @version SDK: 1.10.0 | ThoughtSpot: 8.2.0.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.AddRemoveColumns, payload => { + * console.log('AddRemoveColumns', payload); + * }) + * ``` + */ + AddRemoveColumns = "addRemoveColumns", + /** + * A custom action has been triggered. + * @returns actionId - ID of the custom action + * @returns payload {@link CustomActionPayload} - Response payload with the + * Answer or Liveboard data + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.customAction, payload => { + * const data = payload.data; + * if (data.id === 'insert Custom Action ID here') { + * console.log('Custom Action event:', data.embedAnswerData); + * } + * }) + * ``` + */ + CustomAction = "customAction", + /** + * Listen to double click actions on a visualization. + * @return ContextMenuInputPoints - Data point that is double-clicked + * @version SDK: 1.5.0 | ThoughtSpot: ts7.oct.cl, 7.2.1 + * @example + * ```js + * LiveboardEmbed.on(EmbedEvent.VizPointDoubleClick, payload => { + * console.log('VizPointDoubleClick', payload); + * }) + * ``` + */ + VizPointDoubleClick = "vizPointDoubleClick", + /** + * Listen to clicks on a visualization in a Liveboard or Search result. + * @return viz, clickedPoint - metadata about the point that is clicked + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @important + * @example + * ```js + * embed.on(EmbedEvent.VizPointClick, ({data}) => { + * console.log( + * data.vizId, // viz id + * data.clickedPoint.selectedAttributes[0].value, + * data.clickedPoint.selectedAttributes[0].column.name, + * data.clickedPoint.selectedMeasures[0].value, + * data.clickedPoint.selectedMeasures[0].column.name, + * ) + * }); + * ``` + */ + VizPointClick = "vizPointClick", + /** + * Fired when an error occurs in the embedded component. + * + * **Important:** This event fires for many reasons — including internal + * validation warnings (e.g. `HOST_EVENT_VALIDATION`), configuration issues, + * and transient errors that ThoughtSpot already handles gracefully inside the + * iframe. **Do not call `embed.destroy()` or unmount the embed component on + * every error.** Doing so will tear down the iframe and abort all in-flight + * requests, causing the embed to fail entirely. + * + * Only treat the following codes as unrecoverable: + * - `INIT_ERROR` — SDK was not initialised before render + * - `LOGIN_FAILED` — authentication could not be completed + * + * All other error codes should be logged and inspected, not acted upon + * destructively. + * + * **Note:** There is currently no dedicated event for a true unrecoverable + * crash. A future `EmbedEvent.FatalError` event is planned to give customers + * a clean signal for when the embed cannot recover and needs to be torn down. + * + * Error types include: + * `API` - API call failure. + * `FULLSCREEN` - Error when presenting a Liveboard in full screen mode. + * `VALIDATION_ERROR` - Internal host event or configuration validation warning. + * + * For more information, see https://developers.thoughtspot.com/docs/events-app-integration#errorType + * @returns error - An error object or message + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * // Recommended pattern — only destroy on truly fatal errors + * embed.on(EmbedEvent.Error, (error) => { + * const FATAL_CODES = ['INIT_ERROR', 'LOGIN_FAILED']; + * if (FATAL_CODES.includes(error.data?.code)) { + * embed.destroy(); + * return; + * } + * // Log all other errors — do not destroy + * console.warn('Embed error (non-fatal):', error); + * }); + * ``` + * @example + * ```js + * // API error + * SearchEmbed.on(EmbedEvent.Error, (error) => { + * console.log(error); + * // { errorType: "API", message: '...', code: '...' } + * }); + * ``` + */ + Error = "Error", + /** + * The embedded object has sent an alert. + * @returns alert - An alert object + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * searchEmbed.on(EmbedEvent.Alert) + * ``` + */ + Alert = "alert", + /** + * The ThoughtSpot authentication session has expired. + * @version SDK: 1.4.0 | ThoughtSpot: ts7.sep.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.AuthExpire, showAuthExpired) + * //show auth expired banner + * function showAuthExpired() { + * document.getElementById("authExpiredBanner"); + * } + * ``` + */ + AuthExpire = "ThoughtspotAuthExpired", + /** + * ThoughtSpot failed to validate the auth session. + * @hidden + */ + AuthFailure = "ThoughtspotAuthFailure", + /** + * ThoughtSpot failed to re validate the auth session. + * @hidden + */ + IdleSessionTimeout = "IdleSessionTimeout", + /** + * ThoughtSpot failed to validate the auth session. + * @hidden + */ + AuthLogout = "ThoughtspotAuthLogout", + /** + * The height of the embedded Liveboard or visualization has been computed. + * @returns data - The height of the embedded Liveboard or visualization + * @hidden + */ + EmbedHeight = "EMBED_HEIGHT", + /** + * The center of visible iframe viewport is calculated. + * @returns data - The center of the visible Iframe viewport. + * @hidden + */ + EmbedIframeCenter = "EmbedIframeCenter", + /** + * Emitted when the **Get Data** action is initiated. + * Applicable to `SearchBarEmbed` only. + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + * @example + * ```js + * searchbarEmbed.on(EmbedEvent.GetDataClick) + * .then(data => { + * console.log('Answer Data:', data); + * }) + * ``` + */ + GetDataClick = "getDataClick", + /** + * Detects the route change. + * @version SDK: 1.7.0 | ThoughtSpot: 8.0.0.cl, 8.4.1.sw + * @example + * ```js + * searchEmbed.on(EmbedEvent.RouteChange, payload => + * console.log('data', payload)) + * ``` + */ + RouteChange = "ROUTE_CHANGE", + /** + * The v1 event type for Data + * @hidden + */ + V1Data = "exportVizDataToParent", + /** + * Emitted when the embed does not have cookie access. This happens + * when third-party cookies are blocked by Safari or other + * web browsers. `NoCookieAccess` can trigger. + * @example + * ```js + * appEmbed.on(EmbedEvent.NoCookieAccess) + * ``` + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 7.2.1.sw + */ + NoCookieAccess = "noCookieAccess", + /** + * Emitted when SAML is complete + * @private + * @hidden + */ + SAMLComplete = "samlComplete", + /** + * Emitted when any modal is opened in the app + * @version SDK: 1.6.0 | ThoughtSpot: ts8.nov.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.DialogOpen, payload => { + * console.log('dialog open', payload); + * }) + * ``` + */ + DialogOpen = "dialog-open", + /** + * Emitted when any modal is closed in the app + * @version SDK: 1.6.0 | ThoughtSpot: ts8.nov.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.DialogClose, payload => { + * console.log('dialog close', payload); + * }) + * ``` + */ + DialogClose = "dialog-close", + /** + * Emitted when the Liveboard shell loads. + * You can use this event as a hook to trigger + * other events on the rendered Liveboard. + * @version SDK: 1.9.1 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.LiveboardRendered, payload => { + console.log('Liveboard is rendered', payload); + }) + * ``` + * The following example shows how to trigger + * `SetVisibleVizs` event using LiveboardRendered embed event: + * @example + * ```js + * const embedRef = useEmbedRef(); + * const onLiveboardRendered = () => { + * embed.trigger(HostEvent.SetVisibleVizs, ['viz1', 'viz2']); + * }; + * ``` + */ + LiveboardRendered = "PinboardRendered", + /** + * Emits all events. + * @version SDK: 1.10.0 | ThoughtSpot: 8.2.0.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.ALL, payload => { + * console.log('Embed Events', payload) + * }) + * ``` + */ + ALL = "*", + /** + * Emitted when an Answer is saved in the app. + * Use start:true to subscribe to when save is initiated, or end:true to subscribe to when save is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //Emit when action starts + * searchEmbed.on(EmbedEvent.Save, payload => { + * console.log('Save', payload) + * }, { + * start: true + * }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.Save, payload => { + * console.log('Save', payload) + * }) + * ``` + */ + Save = "save", + /** + * Emitted when the download action is triggered on an Answer. + * + * **Note**: This event is deprecated in v1.21.0. + * To fire an event when a download action is initiated on a chart or table, + * use `EmbedEvent.DownloadAsPng`, `EmbedEvent.DownloadAsPDF`, + * `EmbedEvent.DownloadAsCSV`, or `EmbedEvent.DownloadAsXLSX` + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Download, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }) + * ``` + */ + Download = "download", + /** + * Emitted when the download action is triggered on an Answer. + * Use start:true to subscribe to when download is initiated, or end:true to + * subscribe to when download is completed. Default is end:true. + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.4.0.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.DownloadAsPng, payload => { + * console.log('download PNG', payload)}, {start: true }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.DownloadAsPng, payload => { + * console.log('download PNG', payload)}) + * ``` + */ + DownloadAsPng = "downloadAsPng", + /** + * Emitted when the Download as PDF action is triggered on an Answer + * Use start:true to subscribe to when download as PDF is initiated, or end:true to + * subscribe to when download as PDF is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.DownloadAsPdf, payload => { + * console.log('download PDF', payload)}, {start: true }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.DownloadAsPdf, payload => { + * console.log('download PDF', payload)}) + * ``` + */ + DownloadAsPdf = "downloadAsPdf", + /** + * Emitted when the Download as CSV action is triggered on an Answer. + * Use start:true to subscribe to when download as CSV is initiated, or end:true to + * subscribe to when download as CSV is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.DownloadAsCSV, payload => { + * console.log('download CSV', payload)}, {start: true }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.DownloadAsCSV, payload => { + * console.log('download CSV', payload)}) + * ``` + */ + DownloadAsCsv = "downloadAsCsv", + /** + * Emitted when the Download as XLSX action is triggered on an Answer. + * Use start:true to subscribe to when download as XLSX is initiated, or end:true to + * subscribe to when download as XLSX is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.DownloadAsXlsx, payload => { + * console.log('download Xlsx', payload)}, { start: true }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.DownloadAsXlsx, payload => { + * console.log('download Xlsx', payload)}) + * ``` + */ + DownloadAsXlsx = "downloadAsXlsx", + /** + * Emitted when the Download Liveboard as Continuous PDF action is triggered + * on a Liveboard. + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.DownloadLiveboardAsContinuousPDF, payload => { + * console.log('download liveboard as continuous PDF', payload)}) + * ``` + */ + DownloadLiveboardAsContinuousPDF = "downloadLiveboardAsContinuousPDF", + /** + * Emitted when an Answer is deleted in the app + * Use start:true to subscribe to when delete is initiated, or end:true to subscribe + * to when delete is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * appEmbed.on(EmbedEvent.AnswerDelete, payload => { + * console.log('delete answer', payload)}, {start: true }) + * //trigger when action is completed + * appEmbed.on(EmbedEvent.AnswerDelete, payload => { + * console.log('delete answer', payload)}) + * ``` + */ + AnswerDelete = "answerDelete", + /** + * Emitted when the AI Highlights action is triggered on a Liveboard + * @version SDK: 1.44.0 | ThoughtSpot: 10.15.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.AIHighlights, (payload) => { + * console.log('AI Highlights', payload); + * }) + * ``` + */ + AIHighlights = "AIHighlights", + /** + * Emitted when a user initiates the Pin action to + * add an Answer to a Liveboard. + * Use start:true to subscribe to when pin is initiated, or end:true to subscribe to + * when pin is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.Pin, payload => { + * console.log('pin', payload) + * }, { + * start: true + * }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.Pin, payload => { + * console.log('pin', payload) + * }) + * ``` + */ + Pin = "pin", + /** + * Emitted when SpotIQ analysis is triggered + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.SpotIQAnalyze, payload => { + * console.log('SpotIQAnalyze', payload) + * }, { + * start: true + * }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.SpotIQAnalyze, payload => { + * console.log('SpotIQ analyze', payload) + * }) + * ``` + */ + SpotIQAnalyze = "spotIQAnalyze", + /** + * Emitted when a user shares an object with another user or group + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.Share, payload => { + * console.log('Share', payload) + * }, { + * start: true + * }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.Share, payload => { + * console.log('Share', payload) + * }) + * ``` + */ + Share = "share", + /** + * Emitted when a user clicks the **Include** action to include a specific value or + * data on a chart or table. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.DrillInclude, payload => { + * console.log('Drill include', payload); + * }) + * ``` + */ + DrillInclude = "context-menu-item-include", + /** + * Emitted when a user clicks the **Exclude** action to exclude a specific value or + * data on a chart or table + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.DrillExclude, payload => { + * console.log('Drill exclude', payload); + * }) + * ``` + */ + DrillExclude = "context-menu-item-exclude", + /** + * Emitted when a column value is copied in the embedded app. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * searchEmbed.on(EmbedEvent.CopyToClipboard, payload => { + * console.log('copy to clipboard', payload); + * }) + * ``` + */ + CopyToClipboard = "context-menu-item-copy-to-clipboard", + /** + * Emitted when a user clicks the **Update TML** action on + * embedded Liveboard. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.UpdateTML) + * }) + * ``` + */ + UpdateTML = "updateTSL", + /** + * Emitted when a user clicks the **Edit TML** action + * on an embedded Liveboard. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.EditTML, payload => { + * console.log('Edit TML', payload); + * }) + * ``` + */ + EditTML = "editTSL", + /** + * Emitted when the **Export TML** action is triggered on an + * an embedded object in the app + * Use start:true to subscribe to when export is initiated, or end:true to subscribe + * to when export is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.ExportTML, payload => { + * console.log('Export TML', payload)}, { start: true }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.ExportTML, payload => { + * console.log('Export TML', payload)}) + * ``` + */ + ExportTML = "exportTSL", + /** + * Emitted when an Answer is saved as a View. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.SaveAsView, payload => { + * console.log('View', payload); + * }) + * ``` + */ + SaveAsView = "saveAsView", + /** + * Emitted when the user creates a copy of an Answer. + * Use start:true to subscribe to when copy and edit is initiated, or end:true to + * subscribe to when copy and edit is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * appEmbed.on(EmbedEvent.CopyAEdit, payload => { + * console.log('Copy and edit', payload)}, {start: true }) + * //emit when action ends + * appEmbed.on(EmbedEvent.CopyAEdit, payload => { + * console.log('Copy and edit', payload)}) + * ``` + */ + CopyAEdit = "copyAEdit", + /** + * Emitted when a user clicks *Show underlying data* on an Answer. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.ShowUnderlyingData, payload => { + * console.log('show data', payload); + * }) + * ``` + */ + ShowUnderlyingData = "showUnderlyingData", + /** + * Emitted when an Answer is switched to a chart or table view. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * searchEmbed.on(EmbedEvent.AnswerChartSwitcher, payload => { + * console.log('switch view', payload); + * }) + * ``` + */ + AnswerChartSwitcher = "answerChartSwitcher", + /** + * Internal event to communicate the initial settings back to the ThoughtSpot app + * @hidden + */ + APP_INIT = "appInit", + /** + * Internal event to clear the cached info + * @hidden + */ + CLEAR_INFO_CACHE = "clearInfoCache", + /** + * Emitted when a user clicks **Show Liveboard details** on a Liveboard + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.LiveboardInfo, payload => { + * console.log('Liveboard details', payload); + * }) + * ``` + */ + LiveboardInfo = "pinboardInfo", + /** + * Emitted when a user clicks on the Favorite icon on a Liveboard + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.AddToFavorites, payload => { + * console.log('favorites', payload); + * }) + * ``` + */ + AddToFavorites = "addToFavorites", + /** + * Emitted when a user clicks **Schedule** on a Liveboard + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Schedule, payload => { + * console.log('Liveboard schedule', payload); + * }) + * ``` + */ + Schedule = "subscription", + /** + * Emitted when a user clicks **Edit** on a Liveboard or visualization + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Edit, payload => { + * console.log('Liveboard edit', payload); + * }) + * ``` + */ + Edit = "edit", + /** + * Emitted when a user clicks *Make a copy* on a Liveboard + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.MakeACopy, payload => { + * console.log('Copy', payload); + * }) + * ``` + */ + MakeACopy = "makeACopy", + /** + * Emitted when a user clicks **Present** on a Liveboard or visualization + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Present) + * ``` + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Present, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * }) + * ``` + */ + Present = "present", + /** + * Emitted when a user clicks **Delete** on a visualization + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Delete, + * {vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + */ + Delete = "delete", + /** + * Emitted when a user clicks Manage schedules on a Liveboard + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SchedulesList) + * ``` + */ + SchedulesList = "schedule-list", + /** + * Emitted when a user clicks **Cancel** in edit mode on a Liveboard + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Cancel) + * ``` + */ + Cancel = "cancel", + /** + * Emitted when a user clicks **Explore** on a visualization + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Explore, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + */ + Explore = "explore", + /** + * Emitted when a user clicks **Copy link** action on a visualization. + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.CopyLink, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + */ + CopyLink = "embedDocument", + /** + * Emitted when a user interacts with cross filters on a + * visualization or Liveboard. + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.0.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.CrossFilterChanged, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + */ + CrossFilterChanged = "cross-filter-changed", + /** + * Emitted when a user right clicks on a visualization (chart or table) + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.0.sw + * @example + * ```js + * LiveboardEmbed.on(EmbedEvent.VizPointRightClick, payload => { + * console.log('VizPointClick', payload) + * }) + * ``` + */ + VizPointRightClick = "vizPointRightClick", + /** + * Emitted when a user clicks **Insert to slide** on a visualization + * @hidden + */ + InsertIntoSlide = "insertInToSlide", + /** + * Emitted when a user changes any filter on a Liveboard. + * Returns filter type and name, column name and ID, and runtime + * filter details. + * @example + * + * ```js + * LiveboardEmbed.on(EmbedEvent.FilterChanged, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.23.0 | ThoughtSpot: 9.4.0.cl, 9.5.0.sw + */ + FilterChanged = "filterChanged", + /** + * Emitted when a user updates a connection on the **Data** page + * @version SDK: 1.27.0 | ThoughtSpot: 9.8.0.cl, 9.8.0.sw + */ + UpdateConnection = "updateConnection", + /** + * Emitted when a user updates a connection on the **Data** page + * @version SDK: 1.27.0 | ThoughtSpot: 9.8.0.cl, 9.8.0.sw + */ + CreateConnection = "createConnection", + /** + * Emitted when name, status (private or public) or filter values of a + * Personalised view is updated. + * This event is deprecated. Use {@link EmbedEvent.UpdatePersonalizedView} instead. + * @returns viewName: string + * @returns viewId: string + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + UpdatePersonalisedView = "updatePersonalisedView", + /** + * Emitted when name, status (private or public) or filter values of a + * Personalized view is updated. + * @returns viewName: string + * @returns viewId: string + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + UpdatePersonalizedView = "updatePersonalisedView", + /** + * Emitted when a Personalised view is saved. + * This event is deprecated. Use {@link EmbedEvent.SavePersonalizedView} instead. + * @returns viewName: string + * @returns viewId: string + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + SavePersonalisedView = "savePersonalisedView", + /** + * Emitted when a Personalized view is saved. + * @returns viewName: string + * @returns viewId: string + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + SavePersonalizedView = "savePersonalisedView", + /** + * Emitted when a Liveboard is reset. + * @returns viewName: string + * @returns viewId: string + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + */ + ResetLiveboard = "resetLiveboard", + /** + * Emitted when a PersonalisedView is deleted. + * This event is deprecated. Use {@link EmbedEvent.DeletePersonalizedView} instead. + * @returns views: string[] + * @returns liveboardId: string + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + DeletePersonalisedView = "deletePersonalisedView", + /** + * Emitted when a PersonalizedView is deleted. + * @returns views: string[] + * @returns liveboardId: string + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + DeletePersonalizedView = "deletePersonalisedView", + /** + * Emitted when a user selects a different Personalized View or + * resets to the original/default view on a Liveboard. + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.ChangePersonalizedView, (data) => { + * console.log(data.viewName); // 'Q4 Revenue' or 'Original View' + * console.log(data.viewId); // '2a021a12-...' or null (default) + * console.log(data.liveboardId); // 'abc123...' + * console.log(data.isPublic); // true | false + * }) + * ``` + * @returns viewName: string - Name of the selected view, + * or 'Original View' when reset to default. + * @returns viewId: string | null - GUID of the selected view, + * or null when reset to default. + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + ChangePersonalizedView = "changePersonalisedView", + /** + * Emitted when a user creates a Worksheet. + * @version SDK: 1.27.0 | ThoughtSpot: 9.8.0.cl, 9.8.0.sw + */ + CreateWorksheet = "createWorksheet", + /** + * Emitted when the *Ask Sage* is initialized. + * @returns viewName: string + * @returns viewId: string + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.29.0 | ThoughtSpot Cloud: 9.12.0.cl + */ + AskSageInit = "AskSageInit", + /** + * Emitted when a Liveboard or visualization is renamed. + * @version SDK: 1.28.0 | ThoughtSpot: 9.10.5.cl, 10.1.0.sw + */ + Rename = "rename", + /** + * + * This event allows developers to intercept search execution + * and implement logic that decides whether Search Data should return + * data or block the search operation. + * + * **Prerequisite**: Set`isOnBeforeGetVizDataInterceptEnabled` to `true` + * to ensure that `EmbedEvent.OnBeforeGetVizDataIntercept` is emitted + * when the embedding application user tries to run a search query. + * + * This framework applies only to `AppEmbed` and `SearchEmbed`. + * @param - Includes the following parameters: + * - `payload`: The payload received from the embed related to the Data API call. + * - `responder`: Contains elements that let developers define whether ThoughtSpot + * will run or block the search operation, and if blocked, which error message to + * provide. + * - `execute` - When `execute` returns `true`, the search is run. + * When `execute` returns `false`, the search is not executed. + * - `error` - Developers can customize the user-facing error message when `execute` + * is `false` by using the `error` parameters in `responder`. + * - `errorText` - The error message text shown to the user. + * @version SDK: 1.29.0 | ThoughtSpot: 10.3.0.cl + * @example + * + * This example blocks search operation and returns a custom error message: + * ```js + * embed.on(EmbedEvent.OnBeforeGetVizDataIntercept, (payload, responder) => { + * responder({ + * data: { + * execute: false, + * error: { + * // Provide a custom error message to explain why the search did not run. + * errorText: 'This search query cannot be run. Please contact your administrator for more details.', + * }, + * }, + * }); + * }) + * ``` + * @example + * + * This example allows the search operation to run + * unless the query contains both `sales` and `county`, + * and returns a custom error message if the query is rejected: + * ```js + * embed.on(EmbedEvent.OnBeforeGetVizDataIntercept, (payload, responder) => { + * // Record the search query submitted by the end user. + * const query = payload.data.data.answer.search_query; + * + * responder({ + * data: { + * // Returns true as long as the query does not include both `sales` and `county`. + * execute: !(query.includes('sales') && query.includes('county')), + * error: { + * // Provide a custom error message when the query is blocked by your logic. + * errorText: + * "You can't use this query: " + * + query + * + ". The 'sales' measure can never be used at the 'county' level. " + * + "Please try another measure or remove 'county' from your search.", + * }, + * }, + * }); + * }) + * ``` + */ + OnBeforeGetVizDataIntercept = "onBeforeGetVizDataIntercept", + /** + * Emitted when parameter changes in an Answer + * or Liveboard. + * ```js + * liveboardEmbed.on(EmbedEvent.ParameterChanged, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.29.0 | ThoughtSpot: 10.3.0.cl + */ + ParameterChanged = "parameterChanged", + /** + * Emits when a table visualization is rendered in + * the ThoughtSpot embedded app. + * + * You can also use this event as a hook to trigger host events + * such as `HostEvent.TransformTableVizData` on the table visualization. + * The event payload contains the data used in the rendered table. + * You can extract the relevant data from the payload + * stored in `payload.data.data.columnDataLite`. + * + * `columnDataLite` is a multidimensional array that contains + * data values for each column, which was used in the query to + * generate the table visualization. To find and modify specific cell data, + * you can either loop through the array or directly access a cell if + * you know its position and data index. + * + * In the following code sample, the first cell in the first column + * (`columnDataLite[0].dataValue[0]`) is set to `new fob`. + * Note that any changes made to the data in the payload will only update the + * visual presentation and do not affect the underlying data. + * To persist data value modifications after a reload or during chart + * interactions such as drill down, ensure that the modified + * payload in the `columnDataLite` is passed on to + * `HostEvent.TransformTableVizData` and trigger an update to + * the table visualization. + * + * If the Row-Level Security (RLS) rules are applied on the + * Model, exercise caution when changing column + * or table cell values to maintain data security. + * + * @example + * ```js + * searchEmbed.on(EmbedEvent.TableVizRendered, (payload) => { + * console.log(payload); + * const columnDataLite = payload.data.data.columnDataLite; + * columnDataLite[0].dataValue[0]="new fob"; + * console.log('>>> new Data', columnDataLite); + * searchEmbed.trigger(HostEvent.TransformTableVizData, columnDataLite); + * }) + * ``` + * @version SDK: 1.37.0 | ThoughtSpot: 10.8.0.cl + */ + TableVizRendered = "TableVizRendered", + /** + * Emitted when the liveboard is created from pin modal or Liveboard list page. + * You can use this event as a hook to trigger + * other events on liveboard creation. + * + * ```js + * liveboardEmbed.on(EmbedEvent.CreateLiveboard, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.37.0 | ThoughtSpot: 10.8.0.cl + */ + CreateLiveboard = "createLiveboard", + /** + * Emitted when a user creates a Model. + * @version SDK: 1.37.0 | ThoughtSpot: 10.8.0.cl + */ + CreateModel = "createModel", + /** + * @hidden + * Emitted when a user exits present mode. + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + ExitPresentMode = "exitPresentMode", + /** + * Emitted when a user requests the full height lazy load data. + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + * @hidden + */ + RequestVisibleEmbedCoordinates = "requestVisibleEmbedCoordinates", + /** + * Emitted when Spotter response is text data + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterData, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + SpotterData = "SpotterData", + /** + * Emitted when user opens up the data source preview modal in Spotter embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.PreviewSpotterData, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + PreviewSpotterData = "PreviewSpotterData", + /** + * Emitted when user opens up the Add to Coaching modal on any visualization in Spotter Embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.AddToCoaching, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.45.0 | ThoughtSpot: 26.2.0.cl + */ + AddToCoaching = "addToCoaching", + /** + * Emitted when user opens up the data model instructions modal in Spotter embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.DataModelInstructions, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + */ + DataModelInstructions = "DataModelInstructions", + /** + * Emitted when the Spotter query is triggered in Spotter embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterQueryTriggered, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + SpotterQueryTriggered = "SpotterQueryTriggered", + /** + * Emitted when the last Spotter query is edited in Spotter embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.LastPromptEdited, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + LastPromptEdited = "LastPromptEdited", + /** + * Emitted when the last Spotter query is deleted in Spotter embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.LastPromptDeleted, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + LastPromptDeleted = "LastPromptDeleted", + /** + * Emitted when the conversation is reset in Spotter embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.ResetSpotterConversation, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + ResetSpotterConversation = "ResetSpotterConversation", + /** + * Emitted when the *Spotter* is initialized. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterInit, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.41.0 | ThoughtSpot: 10.12.0.cl + */ + SpotterInit = "spotterInit", + /** + * Emitted when a *Spotter* conversation has been successfully created. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterLoadComplete, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.44.0 | ThoughtSpot: 26.2.0.cl + */ + SpotterLoadComplete = "spotterLoadComplete", + /** + * @hidden + * Triggers when the embed listener is ready to receive events. + * This is used to trigger events after the embed container is loaded. + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.EmbedListenerReady, () => { + * console.log('EmbedListenerReady'); + * }) + * ``` + */ + EmbedListenerReady = "EmbedListenerReady", + /** + * Emitted when the organization is switched. + * @example + * ```js + * appEmbed.on(EmbedEvent.OrgSwitched, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.41.0 | ThoughtSpot: 10.12.0.cl + */ + OrgSwitched = "orgSwitched", + /** + * Emitted when the user intercepts a URL. + * + * Supported on all embed types. + * + * @example + * + * ```js + * embed.on(EmbedEvent.ApiIntercept, (payload, responder) => { + * console.log('payload', payload); + * responder({ + * data: { + * execute: false, + * error: { + * errorText: 'Error Occurred', + * } + * } + * }) + * }) + * ``` + * + * ```js + * // We can also send a response for the intercepted api + * embed.on(EmbedEvent.ApiIntercept, (payload, responder) => { + * console.log('payload', payload); + * responder({ + * data: { + * execute: false, + * response: { + * body: { + * data: { + * // Some api response + * }, + * } + * } + * } + * }) + * }) + * + * // here embed will use the response from the responder as the response for the api + * ``` + * + * ```js + * // We can also send error in response for the intercepted api + * embed.on(EmbedEvent.ApiIntercept, (payload, responder) => { + * console.log('payload', payload); + * responder({ + * data: { + * execute: false, + * response: { + * body: { + * errors: [{ + * title: 'Error Occurred', + * description: 'Error Description', + * isUserError: true, + * }], + * data: {}, + * }, + * } + * } + * }) + * }) + * ``` + * @version SDK: 1.43.0 | ThoughtSpot: 10.15.0.cl + */ + ApiIntercept = "ApiIntercept", + /** + * Emitted when a Spotter conversation is renamed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterConversationRenamed, (payload) => { + * console.log('Conversation renamed', payload); + * // payload: { convId: string, oldTitle: string, newTitle: string } + * }) + * ``` + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + */ + SpotterConversationRenamed = "spotterConversationRenamed", + /** + * Emitted when a Spotter conversation is deleted. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterConversationDeleted, (payload) => { + * console.log('Conversation deleted', payload); + * // payload: { convId: string, title: string } + * }) + * ``` + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + */ + SpotterConversationDeleted = "spotterConversationDeleted", + /** + * Emitted when a Spotter conversation is selected/clicked. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterConversationSelected, (payload) => { + * console.log('Conversation selected', payload); + * // payload: { convId: string, title: string, worksheetId: string } + * }) + * ``` + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + */ + SpotterConversationSelected = "spotterConversationSelected", + /** + * @hidden + * Emitted when the auth token is about to get expired and needs to be refreshed. + * @example + * ```js + * embed.on(EmbedEvent.RefreshAuthToken, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.45.2 | ThoughtSpot: 26.3.0.cl + */ + RefreshAuthToken = "RefreshAuthToken", + /** + * Triggered whenever the page context changes, returning the current context along with the navigation stack. + * @example + * ```js + * embed.on(EmbedEvent.EmbedPageContextChanged, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.47.2 | ThoughtSpot: 26.3.0.cl + */ + EmbedPageContextChanged = "EmbedPageContextChanged", + /** + * Represents a special embed event that is triggered whenever any host event is subscribed. + * + * You can listen to this event when you need to dispatch a host event during load or render, + * particularly in situations where timing issues may occur. + * + * @example + * ```js + * embed.on(`${HostEvent.Save} Subscribed`, () => { + * // make action + * }); + * ``` + * + * @example + * ```js + * embed.on(subscribedEvent(HostEvent.Save), () => { + * // make action + * }); + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.4.0.cl + */ + Subscribed = "Subscribed", + /** + * Emitted when a user clicks the **Send Test Email** button in the + * Liveboard schedule modal. Requires `isSendNowLiveboardSchedulingEnabled` + * to be enabled. + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SendTestScheduleEmail, (payload) => { + * console.log('Send test email', payload); + * // payload: { liveboardId: string, sendToSelf: boolean } + * }) + * ``` + * @version SDK: 1.48.0 | ThoughtSpot Cloud: 26.5.0.cl + */ + SendTestScheduleEmail = "sendTestScheduleEmail", + /** + * Emitted when the SpotterViz panel mounts in embed mode. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizInit, (payload) => { + * console.log('SpotterViz initialized', payload); + * // payload: { liveboardId: string } + * }) + * ``` + */ + SpotterVizInit = "SpotterVizInit", + /** + * Emitted when the user submits a prompt in the SpotterViz panel. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizQueryTriggered, (payload) => { + * console.log('SpotterViz query triggered', payload); + * // payload: { query: string, sessionId: string } + * }) + * ``` + */ + SpotterVizQueryTriggered = "SpotterVizQueryTriggered", + /** + * Emitted when the SpotterViz agent finishes responding. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizResponseComplete, (payload) => { + * console.log('SpotterViz response complete', payload); + * // payload: { sessionId: string, messageId: string } + * }) + * ``` + */ + SpotterVizResponseComplete = "SpotterVizResponseComplete", + /** + * Emitted when a checkpoint is created in the SpotterViz panel. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizCheckpointCreated, (payload) => { + * console.log('SpotterViz checkpoint created', payload); + * // payload: { checkpointId: string, source: string, label: string } + * }) + * ``` + */ + SpotterVizCheckpointCreated = "SpotterVizCheckpointCreated", + /** + * Emitted when a checkpoint is restored in the SpotterViz panel. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizCheckpointRestored, (payload) => { + * console.log('SpotterViz checkpoint restored', payload); + * // payload: { checkpointId: string, newGenNumber: number } + * }) + * ``` + */ + SpotterVizCheckpointRestored = "SpotterVizCheckpointRestored", + /** + * Emitted when an error occurs in the SpotterViz panel. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizError, (payload) => { + * console.log('SpotterViz error', payload); + * }) + * ``` + */ + SpotterVizError = "SpotterVizError", + /** + * Emitted when the SpotterViz panel is closed. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizClosed, (payload) => { + * console.log('SpotterViz panel closed', payload); + * }) + * ``` + */ + SpotterVizClosed = "SpotterVizClosed", + /** + * Emitted when a user clicks the **Refresh** button in the + * Liveboard header. Requires `enableLiveboardDataCache` + * to be enabled. + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.RefreshLiveboardBrowserCache, (payload) => { + * console.log('Liveboard browser cache refreshed', payload); + * // payload: { liveboardId: string } + * }) + * ``` + * @version SDK: 1.49.0 | ThoughtSpot Cloud: 26.6.0.cl + */ + RefreshLiveboardBrowserCache = "refreshLiveboardBrowserCache" +} +/** + * Event types that can be triggered by the host application + * to the embedded ThoughtSpot app. + * + * To trigger an event use the corresponding + * {@link LiveboardEmbed.trigger} or {@link AppEmbed.trigger} or {@link + * SearchEmbed.trigger} method. + * @example + * ```js + * import { HostEvent } from '@thoughtspot/visual-embed-sdk'; + * // Or + * // const { HostEvent } = window.tsembed; + * + * // create the liveboard embed. + * + * liveboardEmbed.trigger(HostEvent.UpdateRuntimeFilters, [ + * { columnName: 'state', operator: RuntimeFilterOp.EQ, values: ["california"]} + * ]); + * ``` + * @example + * If using React components to embed, use the format shown in this example: + * + * ```js + * const selectVizs = () => { + * embedRef.current.trigger(HostEvent.SetVisibleVizs, [ + * "715e4613-c891-4884-be44-aa8d13701c06", + * "3f84d633-e325-44b2-be25-c6650e5a49cf" + * ]); + * }; + * ``` + * + * + * You can also attach an Embed event to a Host event to trigger + * a specific action as shown in this example: + * @example + * ```js + * const EmbeddedComponent = () => { + * const embedRef = useRef(null); // import { useRef } from react + * const onLiveboardRendered = () => { + * embedRef.current.trigger(HostEvent.SetVisibleVizs, ['viz1', 'viz2']); + * }; + * + * return ( + * + * ); + * } + * ``` + * + * **Context Parameter (SDK: 1.45.2+)** + * + * Starting from SDK version 1.45.2 | ThoughtSpot: 26.3.0.cl, you can optionally pass a + * `ContextType` as the third parameter to the `trigger` method to specify the context + * from which the event is triggered. This helps ThoughtSpot understand the current page + * context (Search, Answer, Liveboard, or Spotter) for better event handling. + * + * @example + * ```js + * import { HostEvent, ContextType } from '@thoughtspot/visual-embed-sdk'; + * + * // Trigger Pin event with Search context + * appEmbed.trigger(HostEvent.Pin, { + * vizId: "123", + * liveboardId: "456" + * }, ContextType.Search); + * ``` + * + * @group Events + */ +export declare enum HostEvent { + /** + * Triggers a search operation with the search tokens specified in + * the search query string. + * Supported in `AppEmbed` and `SearchEmbed` deployments. + * Includes the following properties: + * @param - Includes the following keys: + * - `searchQuery`: Query string with search tokens. + * - `dataSources`: Data source GUID to search on. + * Although an array, only a single source is supported. + * - `execute`: Executes search and updates the existing query. + * @example + * ```js + * searchEmbed.trigger(HostEvent.Search, { + searchQuery: "[sales] by [item type]", + dataSources: ["cd252e5c-b552-49a8-821d-3eadaa049cca"], + execute: true + }); + * ``` + * @example + * ```js + * // Trigger search from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Search, { + * searchQuery: "[revenue] by [region]", + * dataSources: ["cd252e5c-b552-49a8-821d-3eadaa049cca"], + * execute: true + * }, ContextType.Search); + * ``` + */ + Search = "search", + /** + * Triggers a drill on certain points of the specified column + * Includes the following properties: + * @param - Includes the following keys: + * - `points`: An object containing `selectedPoints` and/or `clickedPoint` + * to drill to. For example, `{ selectedPoints: [] }`. + * - `columnGuid`: Optional. GUID of the column to drill by. If not provided, + * it will auto drill by the configured column. + * - `autoDrillDown`: Optional. If `true`, the drill down will be done automatically + * on the most popular column. + * - `vizId` (TS >= 9.8.0): Optional. The GUID of the visualization to drill in case + * of a Liveboard. In Spotter embed, `vizId` refers to the Answer ID and is + * **required**. + * @example + * ```js + * searchEmbed.on(EmbedEvent.VizPointDoubleClick, (payload) => { + * console.log(payload); + * const clickedPoint = payload.data.clickedPoint; + * const selectedPoint = payload.data.selectedPoints; + * console.log('>>> called', clickedPoint); + * searchEmbed.trigger(HostEvent.DrillDown, { + * points: { + * clickedPoint, + * selectedPoints: selectedPoint + * }, + * autoDrillDown: true, + * }); + * }) + * ``` + * @example + * ```js + * // Works with TS 9.8.0 and above + * + * liveboardEmbed.on(EmbedEvent.VizPointDoubleClick, (payload) => { + * console.log(payload); + * const clickedPoint = payload.data.clickedPoint; + * const selectedPoint = payload.data.selectedPoints; + * console.log('>>> called', clickedPoint); + * liveboardEmbed.trigger(HostEvent.DrillDown, { + * points: { + * clickedPoint, + * selectedPoints: selectedPoint + * }, + * columnGuid: "", + * vizId: payload.data.vizId + * }); + * }) + * ``` + * @example + * ```js + * // Drill down from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.DrillDown, { + * points: { clickedPoint, selectedPoints }, + * autoDrillDown: true + * }, ContextType.Answer); + * ``` + * @example + * ```js + * // Drill down from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.DrillDown, { + * points: { clickedPoint, selectedPoints }, + * columnGuid: "column-guid" + * }, ContextType.Search); + * ``` + * @version SDK: 1.5.0 | ThoughtSpot: ts7.oct.cl, 7.2.1 + */ + DrillDown = "triggerDrillDown", + /** + * Apply filters + * @hidden + */ + Filter = "filter", + /** + * Reload the Answer or visualization + * @hidden + */ + Reload = "reload", + /** + * Get iframe URL for the current embed view. + * @example + * ```js + * const url = embed.trigger(HostEvent.GetIframeUrl); + * console.log("iFrameURL",url); + * ``` + * @example + * ```js + * // Get iframe URL from specific context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * const url = await appEmbed.trigger(HostEvent.GetIframeUrl, {}, ContextType.Answer); + * console.log("iFrameURL", url); + * ``` + * @version SDK: 1.35.0 | ThoughtSpot: 10.4.0.cl + */ + GetIframeUrl = "GetIframeUrl", + /** + * Display specific visualizations on a Liveboard. + * @param - An array of GUIDs of the visualization to show. The visualization IDs not passed + * in this parameter will be hidden. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SetVisibleVizs, [ + * '730496d6-6903-4601-937e-2c691821af3c', + * 'd547ec54-2a37-4516-a222-2b06719af726']) + * ``` + * @example + * ```js + * // Set visible vizs from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SetVisibleVizs, [ + * '730496d6-6903-4601-937e-2c691821af3c', + * 'd547ec54-2a37-4516-a222-2b06719af726' + * ], ContextType.Liveboard); + * ``` + * @version SDK: 1.6.0 | ThoughtSpot: ts8.nov.cl, 8.4.1.sw + */ + SetVisibleVizs = "SetPinboardVisibleVizs", + /** + * Set a Liveboard tab as an active tab. + * @param - tabId - string of id of Tab to show + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SetActiveTab,{ + * tabId:'730496d6-6903-4601-937e-2c691821af3c' + * }) + * ``` + * @example + * ```js + * // Set active tab from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SetActiveTab, { + * tabId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.24.0 | ThoughtSpot: 9.5.0.cl, 9.5.1-sw + */ + SetActiveTab = "SetActiveTab", + /** + * Updates the runtime filters applied on a Liveboard. The filter + * attributes passed with this event are appended to the existing runtime + * filters applied on a Liveboard. + * + * **Note**: `HostEvent.UpdateRuntimeFilters` is supported in `LiveboardEmbed` + * and `AppEmbed` only. In full application embedding, this event updates + * the runtime filters applied on the Liveboard and saved Answer objects. + * + * @param - Array of {@link RuntimeFilter} objects. Each item includes: + * - `columnName`: Name of the column to filter on. + * - `operator`: {@link RuntimeFilterOp} to apply. For more information, see + * link:https://developers.thoughtspot.com/docs/runtime-filters#rtOperator[Developer Documentation]. + * - `values`: List of operands. Some operators such as EQ and LE allow a single + * value, whereas BW and IN accept multiple values. + * + * **Note**: Updating runtime filters resets the ThoughtSpot + * object to its original state and applies new filter conditions. + * Any user changes (like drilling into a visualization) + * will be cleared, restoring the original visualization + * with the updated filters. + * + + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateRuntimeFilters, [ + * {columnName: "state",operator: RuntimeFilterOp.EQ,values: ["michigan"]}, + * {columnName: "item type",operator: RuntimeFilterOp.EQ,values: ["Jackets"]} + * ]) + * ``` + * @example + * ```js + * // Update runtime filters from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.UpdateRuntimeFilters, [ + * {columnName: "region", operator: RuntimeFilterOp.EQ, values: ["west"]}, + * {columnName: "product", operator: RuntimeFilterOp.IN, values: ["shoes", "boots"]} + * ], ContextType.Liveboard); + * ``` + * @version SDK: 1.9.0 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + * @important + */ + UpdateRuntimeFilters = "UpdateRuntimeFilters", + /** + * Navigate to a specific page in the embedded ThoughtSpot application. + * This is the same as calling `appEmbed.navigateToPage(path, true)`. + * @param - `path` - the path to navigate to go forward or back. The path value can + * be a number; for example, `1`, `-1`. + * @example + * ```js + * appEmbed.navigateToPage(-1) + * ``` + * @version SDK: 1.12.0 | ThoughtSpot: 8.4.0.cl, 8.4.1.sw + */ + Navigate = "Navigate", + /** + * Open the filter panel for a particular column. + * Works with Search and Liveboard embed. + * @param - { columnId: string, + * name: string, + * type: ATTRIBUTE/MEASURE, + * dataType: INT64/CHAR/DATE } + * @example + * ```js + * searchEmbed.trigger(HostEvent.OpenFilter, + * {column: { columnId: '', name: 'column name', type: 'ATTRIBUTE', dataType: 'INT64'}}) + * ``` + * @example + * ```js + * LiveboardEmbed.trigger(HostEvent.OpenFilter, + * { column: {columnId: ''}}) + * ``` + * @example + * ```js + * // Open filter from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.OpenFilter, { + * column: { columnId: '', name: 'region', type: 'ATTRIBUTE', dataType: 'CHAR'} + * }, ContextType.Search); + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl + */ + OpenFilter = "openFilter", + /** + * Add columns to the current search query. + * @param - { columnIds: string[] } + * @example + * ```js + * searchEmbed.trigger(HostEvent.AddColumns, { columnIds: ['',''] }) + * ``` + * @example + * ```js + * // Add columns from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.AddColumns, { + * columnIds: ['col-guid-1', 'col-guid-2'] + * }, ContextType.Search); + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl + */ + AddColumns = "addColumns", + /** + * Remove a column from the current search query. + * @param - { columnId: string } + * @example + * ```js + * searchEmbed.trigger(HostEvent.RemoveColumn, { columnId: '' }) + * ``` + * @example + * ```js + * // Remove column from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.RemoveColumn, { + * columnId: 'column-guid' + * }, ContextType.Search); + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl + */ + RemoveColumn = "removeColumn", + /** + * Get the transient state of a Liveboard as encoded content. + * This includes unsaved and ad hoc changes such as + * Liveboard filters, runtime filters applied on visualizations on a + * Liveboard, and Liveboard layout, changes to visualizations such as + * sorting, toggling of legends, and data drill down. + * For more information, see + * link:https://developers.thoughtspot.com/docs/fetch-data-and-report-apis#transient-lb-content[Liveboard data with unsaved changes]. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.getExportRequestForCurrentPinboard).then( + * data=>console.log(data)) + * ``` + * @version SDK: 1.13.0 | ThoughtSpot: 8.5.0.cl, 8.8.1.sw + */ + getExportRequestForCurrentPinboard = "getExportRequestForCurrentPinboard", + /** + * Trigger **Pin** action on an embedded object. + * If no parameters are defined, the pin action is triggered + * for the Answer that the user is currently on + * and a modal opens for Liveboard selection. + * To add an Answer or visualization to a Liveboard programmatically without + * requiring additional user input via the *Pin to Liveboard* modal, define + * the following parameters: + * + * @param - Includes the following keys: + * - `vizId`: GUID of the saved Answer or Spotter visualization ID to pin to a + * Liveboard. + * Optional when pinning a new chart or table generated from a Search query. + * **Required** in Spotter Embed. + * - `liveboardId`: GUID of the Liveboard to pin an Answer. If there is no Liveboard, + * specify the `newLiveboardName` parameter to create a new Liveboard. + * - `tabId`: GUID of the Liveboard tab. Adds the Answer to the Liveboard tab + * specified in the code. + * - `newVizName`: Name string for the Answer or visualization. If defined, + * this parameter adds a new visualization object or creates a copy of the + * Answer or visualization specified in `vizId`. + * Required. + * - `newLiveboardName`: Name string for the Liveboard. + * Creates a new Liveboard object with the specified name. + * - `newTabName`: Name of the tab. Adds a new tab Liveboard specified + * in the code. + * + * @example + * ```js + * const pinResponse = await appEmbed.trigger(HostEvent.Pin, { + * vizId: "123", + * newVizName: "Sales by region", + * liveboardId: "123", + * tabId: "123" + * }); + * ``` + * @example + * ```js + * const pinResponse = await appEmbed.trigger(HostEvent.Pin, { + * newVizName: "Total sales of Jackets", + * liveboardId: "123" + * }); + * ``` + * + * @example + * ```js + * const pinResponse = await searchEmbed.trigger(HostEvent.Pin, { + * newVizName: "Sales by state", + * newLiveboardName: "Sales", + * newTabName: "Products" + * }); + * ``` + * @example + * ```js + * appEmbed.trigger(HostEvent.Pin) + * ``` + * @example + * ```js + + * // You can use the Data event dispatched on each answer creation to get the vizId and use in Pin host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.Pin, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Using context parameter to specify the context type (SDK: 1.45.2+) + * // Pin from a search answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Pin, { + * vizId: "123", + * newVizName: "Sales by region", + * liveboardId: "456" + * }, ContextType.Search); + * ``` + * @example + * ```js + * // Pin from an answer context (explore modal/page) (SDK: 1.45.2+) + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Pin, { + * vizId: "789", + * newVizName: "Revenue trends", + * liveboardId: "456" + * }, ContextType.Answer); + * ``` + * @example + * ```js + * // Pin from a spotter context (SDK: 1.45.2+) + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Pin, { + * vizId: latestSpotterVizId, + * newVizName: "AI-generated insights", + * liveboardId: "456" + * }, ContextType.Spotter); + * ``` + * + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + Pin = "pin", + /** + * Trigger the **Show Liveboard details** action + * on an embedded Liveboard. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.LiveboardInfo) + *``` + * @example + * ```js + * // Show liveboard info from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.LiveboardInfo, {}, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + LiveboardInfo = "pinboardInfo", + /** + * Trigger the **Schedule** action on an embedded Liveboard. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Schedule) + * ``` + * @example + * ```js + * // Schedule from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Schedule, {}, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + Schedule = "subscription", + /** + * Trigger the **Manage schedule** action on an embedded Liveboard + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ScheduleList) + * ``` + * @example + * ```js + * // Manage schedules from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.ScheduleList, {}, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + SchedulesList = "schedule-list", + /** + * Trigger the **Export TML** action on an embedded Liveboard or + * Answer. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ExportTML) + * ``` + * @example + * ```js + * // Export TML from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.ExportTML, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Export TML from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.ExportTML, {}, ContextType.Search); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + ExportTML = "exportTSL", + /** + * Trigger the **Edit TML** action on an embedded Liveboard or + * saved Answers in the full application embedding. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.EditTML) + * ``` + * @example + * ```js + * // Edit TML from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.EditTML, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Edit TML from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.EditTML, {}, ContextType.Search); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + EditTML = "editTSL", + /** + * Trigger the **Update TML** action on an embedded Liveboard. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateTML) + * ``` + * @example + * ```js + * // Update TML from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.UpdateTML, {}, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + UpdateTML = "updateTSL", + /** + * Trigger the **Download PDF** action on an embedded Liveboard, + * visualization or Answer. + * + * @param - `vizId` refers to the Answer ID in Spotter embed and is required in Spotter embed. + * + * **NOTE**: The **Download** > **PDF** action is available on + * visualizations and Answers if the data is in tabular format. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.DownloadAsPdf) + * ``` + * @example + * ```js + + * // You can use the Data event dispatched on each answer creation to get the vizId and use in DownloadAsPdf host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.DownloadAsPdf, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Download as PDF from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.DownloadAsPdf, {}, ContextType.Search); + * ``` + * @example + * ```js + * // Download as PDF from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.DownloadAsPdf, {}, ContextType.Liveboard); + * ``` + * + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + DownloadAsPdf = "downloadAsPdf", + /** + * Trigger the **Download Liveboard as Continuous PDF** action on an + * embedded Liveboard. + * + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.DownloadLiveboardAsContinuousPDF) + * ``` + * + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + DownloadLiveboardAsContinuousPDF = "downloadLiveboardAsContinuousPDF", + /** + * Trigger the **AI Highlights** action on an embedded Liveboard + * + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.AIHighlights) + * ``` + * @version SDK: 1.44.0 | ThoughtSpot: 10.15.0.cl + */ + AIHighlights = "AIHighlights", + /** + * Trigger the **Make a copy** action on a Liveboard, + * visualization, or Answer page. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.MakeACopy) + * ``` + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.MakeACopy, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * @example + * ```js + * vizEmbed.trigger(HostEvent.MakeACopy) + * ``` + * @example + * ```js + * searchEmbed.trigger(HostEvent.MakeACopy) + * ``` + * @example + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in MakeACopy host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.MakeACopy, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Make a copy from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.MakeACopy, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Make a copy from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.MakeACopy, {}, ContextType.Search); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + MakeACopy = "makeACopy", + /** + * Trigger the **Delete** action for a Liveboard. + * @example + * ```js + * appEmbed.trigger(HostEvent.Remove) + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Remove) + * ``` + * @version SDK: 1.37.0 | ThoughtSpot: 10.8.0.cl, 10.10.0.sw + */ + Remove = "delete", + /** + * Trigger the **Explore** action on a visualization. + * @param - an object with `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Explore, {vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * @example + * ```js + * // Explore from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Explore, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + Explore = "explore", + /** + * Trigger the **Create alert** action on a KPI chart + * in a Liveboard or saved Answer. + * @param - an object with `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.CreateMonitor, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }) + * ``` + * @example + * ```js + * searchEmbed.trigger(HostEvent.CreateMonitor) + * ``` + * @example + * ```js + * // Create monitor from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.CreateMonitor, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Create monitor from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.CreateMonitor, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + CreateMonitor = "createMonitor", + /** + * Trigger the **Manage alerts** action on a KPI chart + * in a visualization or saved Answer. + * @param - an object with `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ManageMonitor, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }) + * ``` + * @example + * ```js + * searchEmbed.trigger(HostEvent.ManageMonitor) + * ``` + * @example + * ```js + * vizEmbed.trigger(HostEvent.ManageMonitor) + * ``` + * @example + * ```js + * // Manage monitor from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.ManageMonitor, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Manage monitor from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.ManageMonitor, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + ManageMonitor = "manageMonitor", + /** + * Trigger the **Edit** action on a Liveboard or a visualization + * on a Liveboard. + * + * This event is not supported in visualization embed and search embed. + * @param - Object parameter. Includes the following keys: + * - `vizId`: To trigger the action for a specific visualization in Liveboard embed, + * pass in `vizId` as a key. In Spotter embed, `vizId` refers to the Answer ID and + * is **required**. + * + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Edit) + * ``` + * ```js + * liveboardEmbed.trigger(HostEvent.Edit, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * @example + * ```js + * spotterEmbed.trigger(HostEvent.Edit); + * ``` + * @example + * ```js + * // Using context parameter to edit liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Edit, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Edit from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Edit, {}, ContextType.Search); + * ``` + * * @example + * ```js + * // Edit from spotter context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Edit, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Spotter); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + Edit = "edit", + /** + * Trigger the **Copy link** action on a Liveboard or visualization + * @param - object - to trigger the action for a + * specific visualization in Liveboard embed, pass in `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.CopyLink) + * ``` + * ```js + * liveboardEmbed.trigger(HostEvent.CopyLink, {vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.CopyLink) + * ``` + * @example + * ```js + * // Copy link from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.CopyLink, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Copy link from liveboard visualization context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.CopyLink, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Copy link from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.CopyLink, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Copy link from liveboard visualization context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.CopyLink, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + CopyLink = "embedDocument", + /** + * Trigger the **Present** action on a Liveboard or visualization + * @param - object - to trigger the action for a specific visualization + * in Liveboard embed, pass in `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Present) + * ``` + * ```js + * liveboardEmbed.trigger(HostEvent.Present, {vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.Present) + * ``` + * @example + * ```js + * // Present from liveboard visualization context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Present, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Present from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Present, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Present from liveboard visualization context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Present, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Present from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Present, {}, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + Present = "present", + /** + * Get TML for the current search. + * @example + * ```js + * searchEmbed.trigger(HostEvent.GetTML).then((tml) => { + * console.log( + * tml.answer.search_query // TML representation of the search query + * ); + * }) + * ``` + * @example + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in GetTML host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.GetTML, { + * vizId: latestSpotterVizId + * }).then((tml) => { + * console.log( + * tml.answer.search_query // TML representation of the search query + * ); + * }) + * ``` + * @example + * ```js + * // Get TML from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.GetTML, {}, ContextType.Search).then((tml) => { + * console.log(tml.answer.search_query); + * }); + * ``` + * @example + * ```js + * // Get TML from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.GetTML, {}, ContextType.Search).then((tml) => { + * console.log(tml.answer); + * }); + * ``` + * @version SDK: 1.18.0 | ThoughtSpot: 8.10.0.cl, 9.0.1.sw + * @important + */ + GetTML = "getTML", + /** + * Trigger the **Show underlying data** action on a + * chart or table. + * + * @param - an object with vizId as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ShowUnderlyingData, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.ShowUnderlyingData) + * ``` + * ```js + * searchEmbed.trigger(HostEvent.ShowUnderlyingData) + * ``` + * @example + * ```js + * // Show underlying data from liveboard visualization context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.ShowUnderlyingData, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Show underlying data from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.ShowUnderlyingData, {}, ContextType.Search); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + ShowUnderlyingData = "showUnderlyingData", + /** + * Trigger the **Delete** action for a visualization + * in an embedded Liveboard, or a chart or table + * generated from Search. + * @param - Liveboard embed takes an object with `vizId` as a key. + * Can be left empty if embedding Search or visualization. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Delete, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * searchEmbed.trigger(HostEvent.Delete) + * ``` + * @example + * ```js + * // Delete from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Delete, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Delete from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Delete, {}, ContextType.Search); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + Delete = "onDeleteAnswer", + /** + * Trigger the **SpotIQ analyze** action on a + * chart or table. + * @param - Liveboard embed takes `vizId` as a + * key. Can be left undefined when embedding Search or + * visualization. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SpotIQAnalyze, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.SpotIQAnalyze) + * ``` + * ```js + * searchEmbed.trigger(HostEvent.SpotIQAnalyze) + * ``` + * @example + * ```js + * // SpotIQ analyze from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.SpotIQAnalyze, { vizId: '730496d6-6903-4601-937e-2c691821af3c' }, ContextType.Search); + * ``` + * @example + * ```js + * // SpotIQ analyze from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SpotIQAnalyze, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + SpotIQAnalyze = "spotIQAnalyze", + /** + * Trigger the **Download** action on charts in + * the embedded view. + * Use {@link HostEvent.DownloadAsPng} instead. + * + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + * + * @deprecated from SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl ,9.4.1.sw + * @param - `vizId` refers to the Visualization ID in Spotter embed and is required in Spotter embed. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Download, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * embed.trigger(HostEvent.Download) + * ``` + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in Download host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.Download, { vizId: latestSpotterVizId }); + * ``` + */ + Download = "downloadAsPng", + /** + * Trigger the **Download** > **PNG** action on + * charts in the embedded view. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.DownloadAsPng, + * {vizId:'730496d6-6903-4601-937e-2c691821af3c'}) + * + * vizEmbed.trigger(HostEvent.DownloadAsPng) + * + * searchEmbed.trigger(HostEvent.DownloadAsPng) + * + * // You can use the Data event dispatched on each answer creation to get the vizId and use in DownloadAsPng host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.DownloadAsPng, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Download as PNG from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.DownloadAsPng, {}, ContextType.Search); + * ``` + * + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.4.1.sw + */ + DownloadAsPng = "downloadAsPng", + /** + * Trigger the **Download** > **CSV** action on tables in + * the embedded view. + * @param - `vizId` refers to the Visualization ID in Spotter embed and is required in Spotter embed. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.DownloadAsCsv, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.DownloadAsCsv) + * ``` + * ```js + * searchEmbed.trigger(HostEvent.DownloadAsCsv) + * ``` + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in DownloadAsCsv host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.DownloadAsCsv, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Download as CSV from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.DownloadAsCsv, {}, ContextType.Search); + * ``` + * @example + * ```js + * // Download as CSV from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.DownloadAsCsv, {}, ContextType.Search); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + DownloadAsCsv = "downloadAsCSV", + /** + * Trigger the **Download** > **XLSX** action on tables + * in the embedded view. + * @param - `vizId` refers to the Visualization ID in Spotter embed and is required in Spotter embed. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.DownloadAsXlsx, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.DownloadAsXlsx) + * ``` + * ```js + * searchEmbed.trigger(HostEvent.DownloadAsXlsx) + * ``` + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in DownloadAsXlsx host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.DownloadAsXlsx, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Download as XLSX from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.DownloadAsXlsx, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Download as XLSX from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.DownloadAsXlsx, {}, ContextType.Search); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + DownloadAsXlsx = "downloadAsXLSX", + /** + * Trigger the **Share** action on an embedded + * Liveboard or Answer. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Share) + * ``` + * ```js + * searchEmbed.trigger(HostEvent.Share) + * ``` + * @example + * ```js + * // Share from Liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Share, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Share from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Share, {}, ContextType.Search); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + Share = "share", + /** + * Trigger the **Save** action on a Liveboard, Answer, or Spotter. + * Saves the changes. + * + * @param - `vizId` refers to the Spotter Visualization Id used in Spotter embed. + * It is required and can be retrieved from the data embed event. + * + * @example + * ```js + * // Save changes in a Liveboard + * liveboardEmbed.trigger(HostEvent.Save) + * ``` + * + * ```js + * // Save the current Answer in Search embed + * searchEmbed.trigger(HostEvent.Save) + * ``` + * + * ```js + * // Save a Visualization in Spotter (requires vizId) + * spotterEmbed.trigger(HostEvent.Save, { + * vizId: "730496d6-6903-4601-937e-2c691821af3c" + * }) + * ``` + * + * ```js + * // How to get the vizId in Spotter? + * + * // You can use the Data event dispatched on each answer creation to get the vizId. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.Save, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Save from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Save, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Save from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.Save, {}, ContextType.Search); + * ``` + * + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + Save = "save", + /** + * Trigger the **Sync to Sheets** action on an embedded visualization or Answer + * Sends data from an Answer or Liveboard visualization to a Google sheet. + * @param - an object with `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SyncToSheets, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.SyncToSheets) + * ``` + * @example + * ```js + * // Sync to sheets from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.SyncToSheets, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Sync to sheets from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SyncToSheets, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + SyncToSheets = "sync-to-sheets", + /** + * Trigger the **Sync to Other Apps** action on an embedded visualization or Answer + * Sends data from an Answer or Liveboard visualization to third-party apps such + * as Slack, Salesforce, Microsoft Teams, ServiceNow and so on. + * @param - an object with vizId as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SyncToOtherApps, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.SyncToOtherApps) + * ``` + * @example + * ```js + * // Sync to other apps from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.SyncToOtherApps, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Sync to other apps from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SyncToOtherApps, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + SyncToOtherApps = "sync-to-other-apps", + /** + * Trigger the **Manage pipelines** action on an embedded + * visualization or Answer. + * Allows users to manage ThoughtSpot Sync pipelines. + * @param - an object with `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ManagePipelines, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.ManagePipelines) + * ``` + * @example + * ```js + * // Manage pipelines from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.ManagePipelines, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Manage pipelines from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.ManagePipelines, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + ManagePipelines = "manage-pipeline", + /** + * Reset search operation on the Search or Answer page. + * @example + * ```js + * searchEmbed.trigger(HostEvent.ResetSearch) + * ``` + * ```js + * appEmbed.trigger(HostEvent.ResetSearch) + * ``` + * @example + * ```js + * // Reset search from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.ResetSearch, {}, ContextType.Search); + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.0.1.sw + */ + ResetSearch = "resetSearch", + /** + * Get details of filters applied on the Liveboard. + * Returns arrays containing Liveboard filter and runtime filter elements. + * @example + * ```js + * const data = await liveboardEmbed.trigger(HostEvent.GetFilters); + * console.log('data', data); + * ``` + * @example + * ```js + * // Get filters from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * const data = await liveboardEmbed.trigger(HostEvent.GetFilters, {}, ContextType.Liveboard); + * console.log('filters', data); + * ``` + * @version SDK: 1.23.0 | ThoughtSpot: 9.4.0.cl + */ + GetFilters = "getFilters", + /** + * Update one or several filters applied on a Liveboard. + * @param - Includes the following keys: + * - `filter`: A single filter object containing column name, filter operator, and + * values. + * - `filters`: Multiple filter objects with column name, filter operator, + * and values for each. + * + * Each filter object must include the following attributes: + * + * `column` - Name of the column to filter on. + * + * `oper` - Filter operator, for example, EQ, IN, CONTAINS. + * For information about the supported filter operators, + * see link:https://developers.thoughtspot.com/docs/runtime-filters#rtOperator[Developer Documentation]. + * + * `values` - An array of one or several values. The value definition on the + * data type you choose to filter on. For a complete list of supported data types, + * see + * link:https://developers.thoughtspot.com/docs/runtime-filters#_supported_data_types[Supported + * data types]. + * + * `type` - To update filters for date time, specify the date format type. + * For more information and examples, see link:https://developers.thoughtspot.com/docs/embed-liveboard#_date_filters[Date filters]. + * @example + * ```js + * + * liveboardEmbed.trigger(HostEvent.UpdateFilters, { + * filter: { + * column: "item type", + * oper: "IN", + * values: ["bags","shirts"] + * } + * }); + * ``` + * @example + * ```js + * + * liveboardEmbed.trigger(HostEvent.UpdateFilters, { + * filter: { + * column: "date", + * oper: "EQ", + * values: ["JULY","2023"], + * type: "MONTH_YEAR" + * } + * }); + * ``` + * @example + * + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateFilters, { + * filters: [{ + * column: "Item Type", + * oper: 'IN', + * values: ["bags","shirts"] + * }, + * { + * column: "Region", + * oper: 'IN', + * values: ["West","Midwest"] + * }, + * { + * column: "Date", + * oper: 'EQ', + * values: ["2023-07-31"], + * type: "EXACT_DATE" + * }] + * }); + * ``` + * If there are multiple columns with the same name, consider + * using `WORKSHEET_NAME::COLUMN_NAME` format. + * + * @example + * + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateFilters, { + * filters: [{ + * column: "(Sample) Retail - Apparel::city", + * oper: 'IN', + * values: ["atlanta"] + * }, + * { + * column: "(Sample) Retail - Apparel::Region", + * oper: 'IN', + * values: ["West","Midwest"] + * }] + * }); + * ``` + * @example + * ```js + * // Update filters from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.UpdateFilters, { + * filter: { + * column: "item type", + * oper: "IN", + * values: ["shoes", "boots"] + * } + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.23.0 | ThoughtSpot: 9.4.0.cl + */ + UpdateFilters = "updateFilters", + /** + * Get tab details for the current Liveboard. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.GetTabs).then((tabDetails) => { + * console.log( + * tabDetails // TabDetails of current Liveboard + * ); + * }) + * ``` + * @example + * ```js + * // Get tabs from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.GetTabs, {}, ContextType.Liveboard).then((tabDetails) => { + * console.log('tabs', tabDetails); + * }); + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl + */ + GetTabs = "getTabs", + /** + * Set the visible tabs on a Liveboard. + * @param - an array of ids of tabs to show, the IDs not passed + * will be hidden. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SetVisibleTabs, [ + * '430496d6-6903-4601-937e-2c691821af3c', + * 'f547ec54-2a37-4516-a222-2b06719af726']) + * ``` + * @example + * ```js + * // Set visible tabs from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SetVisibleTabs, [ + * '430496d6-6903-4601-937e-2c691821af3c', + * 'f547ec54-2a37-4516-a222-2b06719af726' + * ], ContextType.Liveboard); + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + */ + SetVisibleTabs = "SetPinboardVisibleTabs", + /** + * Set the hidden tabs on a Liveboard. + * @param - an array of the IDs of the tabs to hide. + * The IDs not passed will be shown. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SetHiddenTabs, [ + * '630496d6-6903-4601-937e-2c691821af3c', + * 'i547ec54-2a37-4516-a222-2b06719af726']) + * ``` + * @example + * ```js + * // Set hidden tabs from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SetHiddenTabs, [ + * '630496d6-6903-4601-937e-2c691821af3c', + * 'i547ec54-2a37-4516-a222-2b06719af726' + * ], ContextType.Liveboard); + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + */ + SetHiddenTabs = "SetPinboardHiddenTabs", + /** + * Get the Answer session for a Search or + * Liveboard visualization. + * + * Note: This event is not typically used directly. Instead, use the + * `getAnswerService()` method on the embed instance to get an AnswerService + * object that provides a more convenient interface for working with answers. + * + * @example + * ```js + * // Preferred way to get an AnswerService + * const service = await embed.getAnswerService(); + * + * // Alternative direct usage (not recommended) + * const {session} = await embed.trigger( + * HostEvent.GetAnswerSession, { + * vizId: '123', // For Liveboard Visualization. + * }) + * ``` + * @example + * ```js + * // Preferred way to get an AnswerService + * const service = await embed.getAnswerService(); + * + * // Alternative direct usage (not recommended) + * const {session} = await embed.trigger( HostEvent.GetAnswerSession ) + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.10.0.cl, 10.1.0.sw + */ + GetAnswerSession = "getAnswerSession", + /** + * Trigger the *Ask Sage* action for visualizations + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.AskSage, + * {vizId:'730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * @version SDK: 1.29.0 | ThoughtSpot Cloud: 9.12.0.cl + */ + AskSage = "AskSage", + /** + * Trigger cross filter update action on a Liveboard. + * + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateCrossFilter, { + * vizId: 'b535c760-8bbe-4e6f-bb26-af56b4129a1e', + * conditions: [ + * { columnName: 'Category', values: ['mfgr#12','mfgr#14'] }, + * { columnName: 'color', values: ['mint','hot'] }, + * ], + * }); + * ``` + * @version SDK: 1.29.0 | ThoughtSpot Cloud: 10.0.0.cl, 10.1.0.sw + */ + UpdateCrossFilter = "UpdateCrossFilter", + /** + * Trigger reset action for a personalized Liveboard view. + * This event is deprecated. Use {@link HostEvent.ResetLiveboardPersonalizedView} instead. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ResetLiveboardPersonalisedView); + * ``` + * @version SDK: 1.29.0 | ThoughtSpot Cloud: 10.1.0.cl, 10.1.0.sw + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + ResetLiveboardPersonalisedView = "ResetLiveboardPersonalisedView", + /** + * Trigger reset action for a personalized Liveboard view. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ResetLiveboardPersonalizedView); + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + ResetLiveboardPersonalizedView = "ResetLiveboardPersonalisedView", + /** + * Triggers an action to update Parameter values on embedded + * Answers, Liveboard, and Spotter answer in Edit mode. + * @param - Includes the following keys for each item: + * - `name`: Name of the parameter. + * - `value`: The value to set for the parameter. + * - `isVisibleToUser`: Optional. To control the visibility of the parameter chip. + * + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateParameters, [{ + * name: "Integer Range Param", + * value: 10, + * isVisibleToUser: false + * }]) + * ``` + * @example + * ```js + * // Update parameters from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.UpdateParameters, [{ + * name: "Region Param", + * value: "West", + * isVisibleToUser: true + * }], ContextType.Liveboard); + * ``` + * @version SDK: 1.29.0 | ThoughtSpot: 10.1.0.cl, 10.1.0.sw + */ + UpdateParameters = "UpdateParameters", + /** + * Triggers GetParameters to fetch the runtime Parameters. + * @param - `vizId` refers to the Answer ID in Spotter embed and is required in Spotter embed. + * ```js + * liveboardEmbed.trigger(HostEvent.GetParameters).then((parameter) => { + * console.log('parameters', parameter); + * }); + * ``` + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in GetParameters host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.GetParameters, { vizId: latestSpotterVizId }); + *``` + * @example + * ```js + * // Get parameters from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.GetParameters, {}, + * ContextType.Liveboard).then((parameters) => { + * console.log('parameters', parameters); + * }); + * ``` + * @version SDK: 1.29.0 | ThoughtSpot: 10.1.0.cl, 10.1.0.sw + */ + GetParameters = "GetParameters", + /** + * Triggers an event to update a personalized view of a Liveboard. + * This event is deprecated. Use {@link HostEvent.UpdatePersonalizedView} instead. + * ```js + * liveboardEmbed.trigger(HostEvent.UpdatePersonalisedView, {viewId: '1234'}) + * ``` + * @example + * ```js + * // Update personalized view from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.UpdatePersonalisedView, { + * viewId: '1234' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.36.0 | ThoughtSpot: 10.6.0.cl + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + UpdatePersonalisedView = "UpdatePersonalisedView", + /** + * Triggers an event to update a personalized view of a Liveboard. + * ```js + * liveboardEmbed.trigger(HostEvent.UpdatePersonalisedView, {viewId: '1234'}) + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + UpdatePersonalizedView = "UpdatePersonalisedView", + /** + * Triggers selection of a specific Personalized View on a + * Liveboard without reloading the embed. Pass either a + * `viewId` (GUID) or `viewName`. If both are provided, `viewId` takes precedence. + * If neither is provided, the Liveboard resets to the original/default view. + * When a `viewName` is provided and multiple views share + * the same name, the first match is selected. + * @example + * ```js + * liveboardEmbed.trigger( + * HostEvent.SelectPersonalizedView, + * { viewId: '2a021a12-1aed-425d-984b-141ee916ce72' }, + * ) + * ``` + * @example + * ```js + * // Select by name + * liveboardEmbed.trigger( + * HostEvent.SelectPersonalizedView, + * { viewName: 'Dr Smith Cardiology' }, + * ) + * ``` + * @example + * ```js + * // Reset to default view + * liveboardEmbed.trigger( + * HostEvent.SelectPersonalizedView, + * {}, + * ) + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + SelectPersonalizedView = "SelectPersonalisedView", + /** + * @hidden + * Notify when info call is completed successfully + * ```js + * liveboardEmbed.trigger(HostEvent.InfoSuccess, data); + * ``` + * @version SDK: 1.36.0 | ThoughtSpot: 10.6.0.cl + */ + InfoSuccess = "InfoSuccess", + /** + * Trigger the save action for an Answer. + * To programmatically save an answer without opening the + * *Describe your Answer* modal, define the `name` and `description` + * properties. + * If no parameters are specified, the save action is + * triggered with a modal to prompt users to + * add a name and description for the Answer. + * @param - Includes the following keys: + * - `vizId`: Refers to the Answer ID in Spotter embed and is **required** in Spotter + * embed. + * - `name`: Optional. Name string for the Answer. + * - `description`: Optional. Description text for the Answer. + * @example + * ```js + * const saveAnswerResponse = await searchEmbed.trigger(HostEvent.SaveAnswer, { + * name: "Sales by states", + * description: "Total sales by states in MidWest" + * }); + * ``` + * @example + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in SaveAnswer host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.SaveAnswer, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Using context parameter to save answer from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * const saveAnswerResponse = await appEmbed.trigger(HostEvent.SaveAnswer, { + * name: "Regional sales analysis", + * description: "Sales breakdown by region" + * }, ContextType.Search); + * ``` + * @example + * ```js + * // Save answer from answer context (explore modal) + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * const saveAnswerResponse = await appEmbed.trigger(HostEvent.SaveAnswer, { + * name: "Modified analysis", + * description: "Updated from explore view" + * }, ContextType.Answer); + * ``` + * @example + * ```js + * // Save answer from spotter context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * const saveAnswerResponse = await appEmbed.trigger(HostEvent.SaveAnswer, { + * vizId: latestSpotterVizId, + * name: "AI insights", + * description: "Generated from Spotter" + * }, ContextType.Spotter); + * ``` + * @version SDK: 1.36.0 | ThoughtSpot: 10.6.0.cl + */ + SaveAnswer = "saveAnswer", + /** + * EmbedApi + * @hidden + */ + UIPassthrough = "UiPassthrough", + /** + * Triggers the table visualization re-render with the updated data. + * Includes the following properties: + * @param - `columnDataLite` - an array of object containing the + * data value modifications retrieved from the `EmbedEvent.TableVizRendered` + * payload.For example, { columnDataLite: []}`. + * + * @example + * ```js + * searchEmbed.on(EmbedEvent.TableVizRendered, (payload) => { + * console.log(payload); + * const columnDataLite = payload.data.data.columnDataLite; + * columnDataLite[0].dataValue[0]="new fob"; + * console.log('>>> new Data', columnDataLite); + * searchEmbed.trigger(HostEvent.TransformTableVizData, columnDataLite); + * }) + * ``` + * @version SDK: 1.37.0 | ThoughtSpot: 10.8.0.cl + */ + TransformTableVizData = "TransformTableVizData", + /** + * Triggers a search operation with the search tokens specified in + * the search query string in spotter embed. + * @param - Includes the following keys: + * - `query`: Text string in Natural Language format. + * - `executeSearch`: Boolean to execute search and update search query. + * @example + * ```js + * spotterEmbed.trigger(HostEvent.SpotterSearch, { + * query: 'revenue per year', + * executeSearch: true, + * }) + * ``` + * @example + * ```js + * // Spotter search from spotter context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * spotterEmbed.trigger(HostEvent.SpotterSearch, { + * query: 'sales by region', + * executeSearch: true + * }, ContextType.Spotter); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + SpotterSearch = "SpotterSearch", + /** + * Edits the last prompt in spotter embed. + * @param - `query`: Text string + * @example + * ```js + * spotterEmbed.trigger(HostEvent.EditLastPrompt, "revenue per year"); + * ``` + * @example + * ```js + * // Edit last prompt from spotter context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * spotterEmbed.trigger(HostEvent.EditLastPrompt, "sales by region", ContextType.Spotter); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + EditLastPrompt = "EditLastPrompt", + /** + * Opens the data source preview modal in Spotter Embed. + * @example + * ```js + * spotterEmbed.trigger(HostEvent.PreviewSpotterData); + * ``` + * @example + * ```js + * // Preview spotter data from spotter context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * spotterEmbed.trigger(HostEvent.PreviewSpotterData, {}, ContextType.Spotter); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + PreviewSpotterData = "PreviewSpotterData", + /** + * Opens the Add to Coaching modal from a visualization in Spotter Embed. + * @param - `vizId ` refers to the Visualization ID in Spotter embed and is required. + * @example + * ```js + * spotterEmbed.trigger(HostEvent.AddToCoaching, { vizId: '730496d6-6903-4601-937e-2c691821af3c' }); + * + * ``` + * @version SDK: 1.45.0 | ThoughtSpot: 26.2.0.cl + */ + AddToCoaching = "addToCoaching", + /** + * Opens the data model instructions modal in Spotter Embed. + * @example + * ```js + * spotterEmbed.trigger(HostEvent.DataModelInstructions); + * ``` + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + */ + DataModelInstructions = "DataModelInstructions", + /** + * Resets the Spotter Embed Conversation. + * @example + * ```js + * spotterEmbed.trigger(HostEvent.ResetSpotterConversation); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + ResetSpotterConversation = "ResetSpotterConversation", + /** + * Deletes the last prompt in spotter embed. + * @example + * ```js + * spotterEmbed.trigger(HostEvent.DeleteLastPrompt); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + DeleteLastPrompt = "DeleteLastPrompt", + /** + * Toggle the visualization to chart or table view. + * @param - `vizId ` refers to the Visualization ID in Spotter embed and is required. + * @example + * ```js + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.AnswerChartSwitcher, { vizId: latestSpotterVizId }); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + AnswerChartSwitcher = "answerChartSwitcher", + /** + * @hidden + * Trigger exit from presentation mode when user exits fullscreen. + * This is automatically triggered by the SDK when fullscreen mode is exited. + * ```js + * liveboardEmbed.trigger(HostEvent.ExitPresentMode); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + ExitPresentMode = "exitPresentMode", + /** + * Triggers the full height lazy load data. + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.RequestVisibleEmbedCoordinates, (payload) => { + * console.log(payload); + * }); + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + * + * @hidden + */ + VisibleEmbedCoordinates = "visibleEmbedCoordinates", + /** + * Trigger the *Spotter* action for visualizations present on the liveboard's vizzes. + * @param - `vizId` refers to the Visualization ID in Spotter embed and is required. + * @example + * ```js + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.AskSpotter, { vizId: latestSpotterVizId }); + * ``` + * @version SDK: 1.41.0 | ThoughtSpot: 10.12.0.cl + */ + AskSpotter = "AskSpotter", + /** + * @hidden + * Triggers the update of the embed params. + * + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateEmbedParams, viewConfig); + * ``` + */ + UpdateEmbedParams = "updateEmbedParams", + /** + * Triggered when the embed needs to be destroyed. This is used to clean up any embed-related resources internally. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.DestroyEmbed); + * ``` + * @version SDK: 1.41.0 | ThoughtSpot: 10.12.0.cl + */ + DestroyEmbed = "EmbedDestroyed", + /** + * Triggers a new conversation in Spotter embed. + * + * This feature is available only when chat history is enabled on your ThoughtSpot + * instance. Contact your admin or ThoughtSpot Support to enable chat history on your + * instance. + * + * @example + * ```js + * spotterEmbed.trigger(HostEvent.StartNewSpotterConversation); + * ``` + * @version SDK: 1.45.0 | ThoughtSpot: 26.2.0.cl + */ + StartNewSpotterConversation = "StartNewSpotterConversation", + /** + * @hidden + * Get the current context of the embedded page. + * + * @example + * ```js + * const context = await liveboardEmbed.trigger(HostEvent.GetPageContext); + * ``` + * @version SDK: 1.45.0 | ThoughtSpot: 26.2.0.cl + */ + GetPageContext = "GetPageContext", + /** + * Trigger the **Send Test Email** action in the Liveboard schedule modal. + * Sends a test schedule email to self or all recipients. + * Requires `isSendNowLiveboardSchedulingEnabled` to be enabled. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SendTestScheduleEmail, { + * sendToSelf: true, + * }) + * ``` + * @example + * ```js + * // Send to all recipients + * liveboardEmbed.trigger(HostEvent.SendTestScheduleEmail, { + * sendToSelf: false, + * }) + * ``` + * @version SDK: 1.48.0 | ThoughtSpot Cloud: 26.5.0.cl + */ + SendTestScheduleEmail = "sendTestScheduleEmail", + /** + * Sends a user message (prompt) to the SpotterViz panel programmatically. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @param query - the prompt text to send. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SpotterVizSendUserMessage, { + * query: 'Show me revenue by region', + * }); + * ``` + */ + SpotterVizSendUserMessage = "SpotterVizSendUserMessage", + /** + * Initializes a new SpotterViz conversation. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.InitSpotterVizConversation); + * ``` + */ + InitSpotterVizConversation = "InitSpotterVizConversation", + /** + * Clears browser cache and fetches new data for liveboard ChartViz Containers. + * Requires `enableLiveboardDataCache` to be enabled. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.RefreshLiveboardBrowserCache); + * ``` + * @version SDK: 1.49.0 | ThoughtSpot Cloud: 26.6.0.cl + */ + RefreshLiveboardBrowserCache = "refreshLiveboardBrowserCache" +} +/** + * The different visual modes that the data sources panel within + * search could appear in, such as hidden, collapsed, or expanded. + */ +export declare enum DataSourceVisualMode { + /** + * The data source panel is hidden. + */ + Hidden = "hide", + /** + * The data source panel is collapsed, but the user can manually expand it. + */ + Collapsed = "collapse", + /** + * The data source panel is expanded, but the user can manually collapse it. + */ + Expanded = "expand" +} +/** + * The query params passed down to the embedded ThoughtSpot app + * containing configuration and/or visual information. + */ +export declare enum Param { + Tsmcp = "tsmcp", + EmbedApp = "embedApp", + DataSources = "dataSources", + DataSourceMode = "dataSourceMode", + DisableActions = "disableAction", + DisableActionReason = "disableHint", + ForceTable = "forceTable", + preventLiveboardFilterRemoval = "preventPinboardFilterRemoval", + SearchQuery = "searchQuery", + HideActions = "hideAction", + HideObjects = "hideObjects", + HostAppUrl = "hostAppUrl", + EnableVizTransformations = "enableVizTransform", + EnableSearchAssist = "enableSearchAssist", + EnableConnectionNewExperience = "newConnectionsExperience", + EnablePendoHelp = "enablePendoHelp", + HideResult = "hideResult", + UseLastSelectedDataSource = "useLastSelectedSources", + Tag = "tag", + HideTagFilterChips = "hideTagFilterChips", + AutoLogin = "autoLogin", + searchTokenString = "searchTokenString", + executeSearch = "executeSearch", + fullHeight = "isFullHeightPinboard", + livedBoardEmbed = "isLiveboardEmbed", + searchEmbed = "isSearchEmbed", + vizEmbed = "isVizEmbed", + StringIDsUrl = "overrideStringIDsUrl", + Version = "sdkVersion", + ViewPortHeight = "viewPortHeight", + ViewPortWidth = "viewPortWidth", + VisibleActions = "visibleAction", + DisableLoginRedirect = "disableLoginRedirect", + visibleVizs = "pinboardVisibleVizs", + LiveboardV2Enabled = "isPinboardV2Enabled", + DataPanelV2Enabled = "enableDataPanelV2", + ShowAlerts = "showAlerts", + Locale = "locale", + CustomStyle = "customStyle", + ForceSAMLAutoRedirect = "forceSAMLAutoRedirect", + AuthType = "authType", + IconSpriteUrl = "iconSprite", + cookieless = "cookieless", + ContextMenuTrigger = "contextMenuEnabledOnWhichClick", + LinkOverride = "linkOverride", + EnableLinkOverridesV2 = "enableLinkOverridesV2", + blockNonEmbedFullAppAccess = "blockNonEmbedFullAppAccess", + ShowInsertToSlide = "insertInToSlide", + PrimaryNavHidden = "primaryNavHidden", + HideProfleAndHelp = "profileAndHelpInNavBarHidden", + NavigationVersion = "navigationVersion", + HideHamburger = "hideHamburger", + HideObjectSearch = "hideObjectSearch", + HideNotification = "hideNotification", + HideApplicationSwitcher = "applicationSwitcherHidden", + HideOrgSwitcher = "orgSwitcherHidden", + HideWorksheetSelector = "hideWorksheetSelector", + DisableWorksheetChange = "disableWorksheetChange", + HideSourceSelection = "hideSourceSelection", + DisableSourceSelection = "disableSourceSelection", + HideEurekaResults = "hideEurekaResults", + HideEurekaSuggestions = "hideEurekaSuggestions", + HideAutocompleteSuggestions = "hideAutocompleteSuggestions", + HideLiveboardHeader = "hideLiveboardHeader", + ShowLiveboardDescription = "showLiveboardDescription", + ShowLiveboardTitle = "showLiveboardTitle", + ShowMaskedFilterChip = "showMaskedFilterChip", + IsLiveboardMasterpiecesEnabled = "isLiveboardMasterpiecesEnabled", + EnableNewChartLibrary = "muzeChartPhase1EnabledGA", + HiddenTabs = "hideTabs", + VisibleTabs = "visibleTabs", + HideTabPanel = "hideTabPanel", + HideSampleQuestions = "hideSampleQuestions", + WorksheetId = "worksheet", + Query = "query", + HideHomepageLeftNav = "hideHomepageLeftNav", + ModularHomeExperienceEnabled = "modularHomeExperience", + HomepageVersion = "homepageVersion", + ListPageVersion = "listpageVersion", + PendoTrackingKey = "additionalPendoKey", + LiveboardHeaderSticky = "isLiveboardHeaderSticky", + IsProductTour = "isProductTour", + HideSearchBarTitle = "hideSearchBarTitle", + HideSageAnswerHeader = "hideSageAnswerHeader", + HideSearchBar = "hideSearchBar", + ClientLogLevel = "clientLogLevel", + ExposeTranslationIDs = "exposeTranslationIDs", + OverrideNativeConsole = "overrideConsoleLogs", + enableAskSage = "enableAskSage", + CollapseSearchBarInitially = "collapseSearchBarInitially", + DataPanelCustomGroupsAccordionInitialState = "dataPanelCustomGroupsAccordionInitialState", + EnableCustomColumnGroups = "enableCustomColumnGroups", + DateFormatLocale = "dateFormatLocale", + NumberFormatLocale = "numberFormatLocale", + CurrencyFormat = "currencyFormat", + Enable2ColumnLayout = "enable2ColumnLayout", + IsFullAppEmbed = "isFullAppEmbed", + IsOnBeforeGetVizDataInterceptEnabled = "isOnBeforeGetVizDataInterceptEnabled", + FocusSearchBarOnRender = "focusSearchBarOnRender", + DisableRedirectionLinksInNewTab = "disableRedirectionLinksInNewTab", + HomePageSearchBarMode = "homePageSearchBarMode", + ShowLiveboardVerifiedBadge = "showLiveboardVerifiedBadge", + ShowLiveboardReverifyBanner = "showLiveboardReverifyBanner", + LiveboardHeaderV2 = "isLiveboardHeaderV2Enabled", + HideIrrelevantFiltersInTab = "hideIrrelevantFiltersAtTabLevel", + IsEnhancedFilterInteractivityEnabled = "isLiveboardPermissionV2Enabled", + SpotterEnabled = "isSpotterExperienceEnabled", + IsUnifiedSearchExperienceEnabled = "isUnifiedSearchExperienceEnabled", + OverrideOrgId = "orgId", + OauthPollingInterval = "oAuthPollingInterval", + IsForceRedirect = "isForceRedirect", + DataSourceId = "dataSourceId", + preAuthCache = "preAuthCache", + ShowSpotterLimitations = "showSpotterLimitations", + CoverAndFilterOptionInPDF = "arePdfCoverFilterPageCheckboxesEnabled", + PrimaryAction = "primaryAction", + isSpotterAgentEmbed = "isSpotterAgentEmbed", + IsLiveboardStylingAndGroupingEnabled = "isLiveboardStylingAndGroupingEnabled", + IsLazyLoadingForEmbedEnabled = "isLazyLoadingForEmbedEnabled", + RootMarginForLazyLoad = "rootMarginForLazyLoad", + isPNGInScheduledEmailsEnabled = "isPNGInScheduledEmailsEnabled", + IsWYSIWYGLiveboardPDFEnabled = "isWYSIWYGLiveboardPDFEnabled", + isLiveboardXLSXCSVDownloadEnabled = "isLiveboardXLSXCSVDownloadEnabled", + isGranularXLSXCSVSchedulesEnabled = "isGranularXLSXCSVSchedulesEnabled", + isSendNowLiveboardSchedulingEnabled = "isSendNowLiveboardSchedulingEnabled", + isCentralizedLiveboardFilterUXEnabled = "isCentralizedLiveboardFilterUXEnabled", + isLinkParametersEnabled = "isLinkParametersEnabled", + EnablePastConversationsSidebar = "enablePastConversationsSidebar", + UpdatedSpotterChatPrompt = "updatedSpotterChatPrompt", + EnableStopAnswerGenerationEmbed = "enableStopAnswerGenerationEmbed", + SpotterSidebarTitle = "spotterSidebarTitle", + SpotterSidebarDefaultExpanded = "spotterSidebarDefaultExpanded", + SpotterChatRenameLabel = "spotterChatRenameLabel", + SpotterChatDeleteLabel = "spotterChatDeleteLabel", + SpotterDeleteConversationModalTitle = "spotterDeleteConversationModalTitle", + SpotterPastConversationAlertMessage = "spotterPastConversationAlertMessage", + SpotterDocumentationUrl = "spotterDocumentationUrl", + SpotterBestPracticesLabel = "spotterBestPracticesLabel", + SpotterConversationsBatchSize = "spotterConversationsBatchSize", + SpotterNewChatButtonTitle = "spotterNewChatButtonTitle", + IsThisPeriodInDateFiltersEnabled = "isThisPeriodInDateFiltersEnabled", + HideToolResponseCardBranding = "hideToolResponseCardBranding", + ToolResponseCardBrandingLabel = "toolResponseCardBrandingLabel", + EnableHomepageAnnouncement = "enableHomepageAnnouncement", + EnableLiveboardDataCache = "enableLiveboardDataCache", + SpotterFileUploadEnabled = "spotterFileUploadEnabled", + SpotterFileUploadFileTypes = "spotterFileUploadFileTypes" +} +/** + * Configuration for allowed file types in Spotter file upload. + * @group Embed components + */ +export type SpotterFileUploadFileTypes = { + types?: string[]; +}; +/** + * ThoughtSpot application pages include actions and menu commands + * for various user-initiated operations. These actions are represented + * as enumeration members in the SDK. To control actions in the embedded view: + * - disabledActions — the action is grayed out and still visible, but non-interactive (user can see but not click). + * - hiddenActions — the action is completely removed from the UI (user cannot see it at all). + * - visibleActions — allowlist, only these actions are shown; all others are hidden. + * + * Use disabledActions to disable (gray out) an action. + * Use hiddenActions to hide (fully remove) an action. + * Use visibleActions to show only specific actions. + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... //other embed view config + * visibleActions: [Action.Save, Action.Edit, Action.Present, Action.Explore], + * disabledActions: [Action.Download], + * //hiddenActions: [], // Set either this or visibleActions + * }) + * ``` + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... //other embed view config + * //visibleActions: [], + * disabledActions: [Action.Download], + * hiddenActions: [Action.Edit, Action.Explore], + * }) + * ``` + * See also link:https://developers.thoughtspot.com/docs/actions[Developer Documentation]. + */ +export declare enum Action { + /** + * The **Save** action on an Answer or Liveboard. + * Allows users to save the changes. + * @example + * ```js + * disabledActions: [Action.Save] + * ``` + */ + Save = "save", + /** + * @hidden + */ + Update = "update", + /** + * @hidden + */ + SaveUntitled = "saveUntitled", + /** + * The **Save as View** action on the Answer + * page. Saves an Answer as a View object in the full + * application embedding mode. + * @example + * ```js + * disabledActions: [Action.SaveAsView] + * ``` + */ + SaveAsView = "saveAsView", + /** + * The **Make a copy** action on a Liveboard or Answer + * page. Creates a copy of the Liveboard. + * In LiveboardEmbed, the **Make a copy** action is not available for + * visualizations in the embedded Liveboard view. + * In AppEmbed, the **Make a copy** action is available on both + * Liveboards and visualizations. + * @example + * ```js + * disabledActions: [Action.MakeACopy] + * ``` + */ + MakeACopy = "makeACopy", + /** + * The **Copy and Edit** action on a Liveboard. + * This action is now replaced with `Action.MakeACopy`. + * @example + * ```js + * disabledActions: [Action.EditACopy] + * ``` + */ + EditACopy = "editACopy", + /** + * The **Copy link** menu action on a Liveboard visualization. + * Copies the visualization URL + * @example + * ```js + * disabledActions: [Action.CopyLink] + * ``` + */ + CopyLink = "embedDocument", + /** + * @hidden + */ + ResetLayout = "resetLayout", + /** + * The **Schedule** menu action on a Liveboard. + * Allows scheduling a Liveboard job, for example, + * sending periodic notifications. + * @example + * ```js + * disabledActions: [Action.Schedule] + * ``` + */ + Schedule = "subscription", + /** + * The **Manage schedules** menu action on a Liveboard. + * Allows users to manage scheduled Liveboard jobs. + * @example + * ```js + * disabledActions: [Action.SchedulesList] + * ``` + */ + SchedulesList = "schedule-list", + /** + * The **Share** action on a Liveboard, Answer, or Model. + * Allows users to share an object with other users and groups. + * @example + * ```js + * disabledActions: [Action.Share] + * ``` + */ + Share = "share", + /** + * The **Add filter** action on a Liveboard page. + * Allows adding filters to visualizations on a Liveboard. + * @example + * ```js + * disabledActions: [Action.AddFilter] + * ``` + */ + AddFilter = "addFilter", + /** + * The **Add Data Panel Objects** action on the data panel v2. + * Allows to show action menu to add different objects (such as + * formulas, Parameters) in data panel new experience. + * @example + * ```js + * disabledActions: [Action.AddDataPanelObjects] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl, 10.1.0.sw + */ + AddDataPanelObjects = "addDataPanelObjects", + /** + * The filter configuration options for a Liveboard. + * The configuration options are available when adding + * filters on a Liveboard. + * + * @example + * ```js + * disabledActions: [Action.ConfigureFilter] + * ``` + */ + ConfigureFilter = "configureFilter", + /** + * The **Collapse data sources** icon on the Search page. + * Collapses the panel showing data sources. + * + * @example + * ```js + * disabledActions: [Action.CollapseDataPanel] + * ``` + * @version SDK: 1.1.0 | ThoughtSpot Cloud: ts7.may.cl, 8.4.1.sw + */ + CollapseDataSources = "collapseDataSources", + /** + * The **Collapse data panel** icon on the Search page. + * Collapses the data panel view. + * + * @version SDK: 1.34.0 | ThoughtSpot Cloud: 10.3.0.cl + * + * @example + * ```js + * disabledActions: [Action.CollapseDataPanel] + * ``` + */ + CollapseDataPanel = "collapseDataPanel", + /** + * The **Choose sources** button on Search page. + * Allows selecting data sources for search queries. + * @example + * ```js + * disabledActions: [Action.ChooseDataSources] + * ``` + */ + ChooseDataSources = "chooseDataSources", + /** + * The **Create formula** action on a Search or Answer page. + * Allows adding formulas to an Answer. + * @example + * ```js + * disabledActions: [Action.AddFormula] + * ``` + */ + AddFormula = "addFormula", + /** + * The **Add parameter** action on a Liveboard or Answer. + * Allows adding Parameters to a Liveboard or Answer. + * @example + * ```js + * disabledActions: [Action.AddParameter] + * ``` + */ + AddParameter = "addParameter", + /** + * The **Add Column Set** action on a Answer. + * Allows adding column sets to a Answer. + * @example + * ```js + * disabledActions: [Action.AddColumnSet] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl, 10.1.0.sw + */ + AddColumnSet = "addSimpleCohort", + /** + * The **Add Query Set** action on a Answer. + * Allows adding query sets to a Answer. + * @example + * ```js + * disabledActions: [Action.AddQuerySet] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl, 10.1.0.sw + */ + AddQuerySet = "addAdvancedCohort", + /** + * @hidden + */ + SearchOnTop = "searchOnTop", + /** + * The **SpotIQ analyze** menu action on a visualization or + * Answer page. + * @example + * ```js + * disabledActions: [Action.SpotIQAnalyze] + * ``` + */ + SpotIQAnalyze = "spotIQAnalyze", + /** + * @hidden + */ + ExplainInsight = "explainInsight", + /** + * @hidden + */ + SpotIQFollow = "spotIQFollow", + /** + * The Share action for a Liveboard visualization. + */ + ShareViz = "shareViz", + /** + * @hidden + */ + ReplaySearch = "replaySearch", + /** + * The **Show underlying data** menu action on a + * visualization or Answer page. + * Displays detailed information and raw data + * for a given visualization. + * @example + * ```js + * disabledActions: [Action.ShowUnderlyingData] + * ``` + */ + ShowUnderlyingData = "showUnderlyingData", + /** + * The **Download** menu action on Liveboard + * visualizations and Answers. + * Allows downloading a visualization or Answer. + * @example + * ```js + * disabledActions: [Action.DownloadAsPng] + * ``` + */ + Download = "download", + /** + * The **Download** > **PNG** menu action for charts on a Liveboard + * or Answer page. + * Downloads a visualization or Answer as a PNG file. + * @example + * ```js + * disabledActions: [Action.DownloadAsPng] + * ``` + */ + DownloadAsPng = "downloadAsPng", + /** + * + *The **Download PDF** action that downloads a Liveboard, + * visualization, or Answer as a PDF file. + * + * **NOTE**: The **Download** > **PDF** option is available for + * tables in visualizations and Answers. + * @example + * ```js + * disabledActions: [Action.DownloadAsPdf] + * ``` + */ + DownloadAsPdf = "downloadAsPdf", + /** + * The **Download** > **CSV** menu action for tables on a Liveboard + * or Answer page. + * Downloads a visualization or Answer in the XLSX format. + * @example + * ```js + * disabledActions: [Action.DownloadAsCsv] + * ``` + */ + DownloadAsCsv = "downloadAsCSV", + /** + * The **Download** > **XLSX** menu action for tables on a Liveboard + * or Answer page. + * Downloads a visualization or Answer in the XLSX format. + * @example + * ```js + * disabledActions: [Action.DownloadAsXlsx] + * ``` + */ + DownloadAsXlsx = "downloadAsXLSX", + /** + * The **Download Liveboard** menu action on a Liveboard. + * Allows downloading the entire Liveboard. + * @example + * ```js + * disabledActions: [Action.DownloadLiveboard] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + DownloadLiveboard = "downloadLiveboard", + /** + * The **Download Liveboard as Continuous PDF** menu action on a Liveboard. + * Allows downloading the entire Liveboard as a continuous PDF. + * @example + * ```js + * disabledActions: [Action.DownloadLiveboardAsContinuousPDF] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + DownloadLiveboardAsContinuousPDF = "downloadLiveboardAsContinuousPDF", + /** + * The Download Liveboard as A4 PDF menu action on a Liveboard. + * Allows downloading the entire Liveboard as an A4 PDF. + * Requires {@link Action.DownloadLiveboard} as a parent action when + * {@link LiveboardViewConfig.isLiveboardXLSXCSVDownloadEnabled} or + * {@link LiveboardViewConfig.isContinuousLiveboardPDFEnabled} flags are enabled. + * Use this instead of {@link Action.DownloadAsPdf} when either flag is on. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * disabledActions: [Action.DownloadLiveboardAsA4Pdf] + * ``` + */ + DownloadLiveboardAsA4Pdf = "downloadLiveboardAsA4Pdf", + /** + * The **Download Liveboard as XLSX** menu action on a Liveboard. + * Allows downloading the entire Liveboard as an XLSX file. + * @example + * ```js + * disabledActions: [Action.DownloadLiveboardAsXlsx] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + DownloadLiveboardAsXlsx = "downloadLiveboardAsXlsx", + /** + * The **Download Liveboard as CSV** menu action on a Liveboard. + * Allows downloading the entire Liveboard as a CSV file. + * @example + * ```js + * disabledActions: [Action.DownloadLiveboardAsCsv] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + DownloadLiveboardAsCsv = "downloadLiveboardAsCsv", + /** + * @hidden + */ + DownloadTrace = "downloadTrace", + /** + * The **Export TML** menu action on a Liveboard, Answer, and + * the Data Workspace pages for data objects and connections. + * + * Allows exporting an object as a TML file. + * + * @example + * ```js + * disabledActions: [Action.ExportTML] + * ``` + */ + ExportTML = "exportTSL", + /** + * The **Import TML** menu action on the + * *Data Workspace* > *Utilities* page. + * Imports TML representation of ThoughtSpot objects. + * @example + * ```js + * disabledActions: [Action.ImportTML] + * ``` + */ + ImportTML = "importTSL", + /** + * The **Update TML** menu action for Liveboards and Answers. + * Updates TML representation of ThoughtSpot objects. + * @example + * ```js + * disabledActions: [Action.UpdateTML] + * ``` + */ + UpdateTML = "updateTSL", + /** + * The **Edit TML** menu action for Liveboards and Answers. + * Opens the TML editor. + * @example + * ```js + * disabledActions: [Action.EditTML] + * ``` + */ + EditTML = "editTSL", + /** + * The **Present** menu action for Liveboards and Answers. + * Allows presenting a Liveboard or visualization in + * slideshow mode. + * @example + * ```js + * disabledActions: [Action.Present] + * ``` + */ + Present = "present", + /** + * The visualization tile resize option. + * Also available via More `...` options menu on a visualization. + * Allows resizing visualization tiles and switching + * between different preset layout option. + * + * @example + * ```js + * disabledActions: [Action.ToggleSize] + * ``` + */ + ToggleSize = "toggleSize", + /** + * The *Edit* action on the Liveboard page and in the + * visualization menu. + * Opens a Liveboard or visualization in edit mode. + * @example + * ```js + * disabledActions: [Action.Edit] + * ``` + */ + Edit = "edit", + /** + * The text edit option for Liveboard and visualization titles. + * @example + * ```js + * disabledActions: [Action.EditTitle] + * ``` + */ + EditTitle = "editTitle", + /** + * The **Delete** action on a Liveboard, *Liveboards* and + * *Answers* list pages in full application embedding. + * + * @example + * ```js + * disabledActions: [Action.Remove] + * ``` + */ + Remove = "delete", + /** + * @hidden + */ + Ungroup = "ungroup", + /** + * @hidden + */ + Describe = "describe", + /** + * @hidden + */ + Relate = "relate", + /** + * @hidden + */ + CustomizeHeadlines = "customizeHeadlines", + /** + * @hidden + */ + PinboardInfo = "pinboardInfo", + /** + * The **Show Liveboard details** menu action on a Liveboard. + * Displays details such as the name, description, and + * author of the Liveboard, and timestamp of Liveboard creation + * and update. + * @example + * ```js + * disabledActions: [Action.LiveboardInfo] + * ``` + */ + LiveboardInfo = "pinboardInfo", + /** + * @hidden + */ + SendAnswerFeedback = "sendFeedback", + /** + * @hidden + */ + DownloadEmbraceQueries = "downloadEmbraceQueries", + /** + * The **Pin** menu action on an Answer or + * Search results page. + * @example + * ```js + * disabledActions: [Action.Pin] + * ``` + */ + Pin = "pin", + /** + * @hidden + */ + AnalysisInfo = "analysisInfo", + /** + * The **Schedule** menu action on a Liveboard. + * Allows scheduling a Liveboard job. + * @example + * ```js + * disabledActions: [Action.Subscription] + * ``` + */ + Subscription = "subscription", + /** + * The **Explore** action on Liveboard visualizations + * @example + * ```js + * disabledActions: [Action.Explore] + * ``` + */ + Explore = "explore", + /** + * The contextual menu action to include a specific data point + * when drilling down a table or chart on an Answer. + * + * @example + * ```js + * disabledActions: [Action.DrillInclude] + * ``` + */ + DrillInclude = "context-menu-item-include", + /** + * The contextual menu action to exclude a specific data point + * when drilling down a table or chart on an Answer. + * @example + * ```js + * disabledActions: [Action.DrillInclude] + * ``` + */ + DrillExclude = "context-menu-item-exclude", + /** + * The **Copy to clipboard** menu action on tables in an Answer + * or Liveboard. + * Copies the selected data point. + * @example + * ```js + * disabledActions: [Action.CopyToClipboard] + * ``` + */ + CopyToClipboard = "context-menu-item-copy-to-clipboard", + CopyAndEdit = "context-menu-item-copy-and-edit", + /** + * @hidden + */ + DrillEdit = "context-menu-item-edit", + EditMeasure = "context-menu-item-edit-measure", + Separator = "context-menu-item-separator", + /** + * The **Drill down** menu action on Answers and Liveboard + * visualizations. + * Allows drilling down to a specific data point on a chart or table. + * @example + * ```js + * disabledActions: [Action.DrillDown] + * ``` + */ + DrillDown = "DRILL", + /** + * The request access action on Liveboards. + * Allows users with view permissions to request edit access to a Liveboard. + * @example + * ```js + * disabledActions: [Action.RequestAccess] + * ``` + */ + RequestAccess = "requestAccess", + /** + * Controls the display and availability of the **Query visualizer** and + * **Query SQL** buttons in the Query details panel on the Answer page. + * + * **Query visualizer** - Displays the tables and filters used in the search query. + * **Query SQL** - Displays the SQL statements used to retrieve data for the query. + * + * Note: This action ID only affects the visibility of the buttons within the + * Query details panel. It does not control the visibility + * of the query details icon on the Answer page. + * @example + * ```js + * disabledActions: [Action.QueryDetailsButtons] + * ``` + */ + QueryDetailsButtons = "queryDetailsButtons", + /** + * The **Delete** action for Answers in the full application + * embedding mode. + * @example + * ```js + * disabledActions: [Action.AnswerDelete] + * ``` + * @version SDK: 1.9.0 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + */ + AnswerDelete = "onDeleteAnswer", + /** + * The chart switcher icon on Answer page and + * visualizations in edit mode. + * Allows switching to the table or chart mode + * when editing a visualization. + * @example + * ```js + * disabledActions: [Action.AnswerChartSwitcher] + * ``` + * @version SDK: 1.9.0 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + */ + AnswerChartSwitcher = "answerChartSwitcher", + /** + * The Favorites icon (*) for Answers, + * Liveboard, and data objects like Model, + * Tables and Views. + * Allows adding an object to the user's favorites list. + * @example + * ```js + * disabledActions: [Action.AddToFavorites] + * ``` + * @version SDK: 1.9.0 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + */ + AddToFavorites = "addToFavorites", + /** + * The edit icon on Liveboards (Classic experience). + * @example + * ```js + * disabledActions: [Action.EditDetails] + * ``` + * @version SDK: 1.9.0 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + */ + EditDetails = "editDetails", + /** + * The *Create alert* action for KPI charts. + * Allows users to schedule threshold-based alerts + * for KPI charts. + * @example + * ```js + * disabledActions: [Action.CreateMonitor] + * ``` + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + */ + CreateMonitor = "createMonitor", + /** + * @version SDK: 1.11.1 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @deprecated This action is deprecated. It was used for reporting errors. + * @example + * ```js + * disabledActions: [Action.ReportError] + * ``` + */ + ReportError = "reportError", + /** + * The **Sync to sheets** action on Answers and Liveboard visualizations. + * Allows sending data to a Google Sheet. + * @example + * ```js + * disabledActions: [Action.SyncToSheets] + * ``` + * @version SDK: 1.18.0 | ThoughtSpot: 8.10.0.cl, 9.0.1.sw + */ + SyncToSheets = "sync-to-sheets", + /** + * The **Sync to other apps** action on Answers and Liveboard visualizations. + * Allows sending data to third-party apps like Slack, Salesforce, + * Microsoft Teams, and so on. + * @example + * ```js + * disabledActions: [Action.SyncToOtherApps] + * ``` + * @version SDK: 1.18.0 | ThoughtSpot: 8.10.0.cl, 9.0.1.sw + */ + SyncToOtherApps = "sync-to-other-apps", + /** + * The **Manage pipelines** action on Answers and Liveboard visualizations. + * Allows users to manage data sync pipelines to third-party apps. + * @example + * ```js + * disabledActions: [Action.ManagePipelines] + * ``` + * @version SDK: 1.18.0 | ThoughtSpot: 8.10.0.cl, 9.0.1.sw + */ + ManagePipelines = "manage-pipeline", + /** + * The **Filter** action on Liveboard visualizations. + * Allows users to apply cross-filters on a Liveboard. + * @example + * ```js + * disabledActions: [Action.CrossFilter] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.8.0.sw + */ + CrossFilter = "context-menu-item-cross-filter", + /** + * The **Sync to Slack** action on Liveboard visualizations. + * Allows sending data to third-party apps like Slack. + * @example + * ```js + * disabledActions: [Action.SyncToSlack] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot Cloud: 10.1.0.cl + */ + SyncToSlack = "syncToSlack", + /** + * The **Sync to Teams** action on Liveboard visualizations. + * Allows sending data to third-party apps like Microsoft Teams. + * @example + * ```js + * disabledActions: [Action.SyncToTeams] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot Cloud: 10.1.0.cl + */ + SyncToTeams = "syncToTeams", + /** + * The **Remove** action that appears when cross filters are applied + * on a Liveboard. + * Removes filters applied to a visualization. + * @example + * ```js + * disabledActions: [Action.RemoveCrossFilter] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + RemoveCrossFilter = "context-menu-item-remove-cross-filter", + /** + * The **Aggregate** option in the chart axis or the + * table column customization menu. + * Provides aggregation options to analyze the data on a chart or table. + * @example + * ```js + * disabledActions: [Action.AxisMenuAggregate] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + AxisMenuAggregate = "axisMenuAggregate", + /** + * The **Time bucket** option in the chart axis or table column + * customization menu. + * Allows defining time metric for date comparison. + * @example + * ```js + * disabledActions: [Action.AxisMenuTimeBucket] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + AxisMenuTimeBucket = "axisMenuTimeBucket", + /** + * The **Filter** action in the chart axis or table column + * customization menu. + * Allows adding, editing, or removing filters. + * + * @example + * ```js + * disabledActions: [Action.AxisMenuFilter] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + AxisMenuFilter = "axisMenuFilter", + /** + * The **Conditional formatting** action on chart or table. + * Allows adding rules for conditional formatting of data + * points on a chart or table. + * @example + * ```js + * disabledActions: [Action.AxisMenuConditionalFormat] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + AxisMenuConditionalFormat = "axisMenuConditionalFormat", + /** + * The **Sort** menu action on a table or chart axis + * Sorts data in ascending or descending order. + * Allows adding, editing, or removing filters. + * @example + * ```js + * disabledActions: [Action.AxisMenuConditionalFormat] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + AxisMenuSort = "axisMenuSort", + /** + * The **Group** option in the chart axis or table column + * customization menu. + * Allows grouping data points if the axes use the same + * unit of measurement and a similar scale. + * @example + * ```js + * disabledActions: [Action.AxisMenuGroup] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + AxisMenuGroup = "axisMenuGroup", + /** + * The **Position** option in the axis customization menu. + * Allows changing the position of the axis to the + * left or right side of the chart. + * @example + * ```js + * disabledActions: [Action.AxisMenuPosition] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + AxisMenuPosition = "axisMenuPosition", + /** + * The **Rename** option in the chart axis or table column customization menu. + * Renames the axis label on a chart or the column header on a table. + * @example + * ```js + * disabledActions: [Action.AxisMenuRename] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + AxisMenuRename = "axisMenuRename", + /** + * The **Edit** action in the axis customization menu. + * Allows editing the axis name, position, minimum and maximum values, + * and format of a column. + * @example + * ```js + * disabledActions: [Action.AxisMenuEdit] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + AxisMenuEdit = "axisMenuEdit", + /** + * The **Number format** action to customize the format of + * the data labels on a chart or table. + * @example + * ```js + * disabledActions: [Action.AxisMenuNumberFormat] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + AxisMenuNumberFormat = "axisMenuNumberFormat", + /** + * The **Text wrapping** action on a table. + * Wraps or clips column text on a table. + * @example + * ```js + * disabledActions: [Action.AxisMenuTextWrapping] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + AxisMenuTextWrapping = "axisMenuTextWrapping", + /** + * The **Remove** action in the chart axis or table column + * customization menu. + * Removes the data labels from a chart or the column of a + * table visualization. + * @example + * ```js + * disabledActions: [Action.AxisMenuRemove] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + AxisMenuRemove = "axisMenuRemove", + /** + * The **Compare with** action in the chart axis customization menu. + * Allows comparing data across dimensions or measures. + * @example + * ```js + * disabledActions: [Action.AxisMenuCompare] + * ``` + * @version SDK: 1.50.0 | ThoughtSpot: 26.7.0.cl + */ + AxisMenuCompare = "axisMenuCompare", + /** + * The **Merge with** action in the chart axis customization menu. + * Allows merging data across dimensions or measures. + * @example + * ```js + * disabledActions: [Action.AxisMenuMerge] + * ``` + * @version SDK: 1.50.0 | ThoughtSpot: 26.7.0.cl + */ + AxisMenuMerge = "axisMenuMerge", + /** + * @hidden + */ + InsertInToSlide = "insertInToSlide", + /** + * The **Rename** menu action on Liveboards and visualizations. + * Allows renaming a Liveboard or visualization. + * @example + * ```js + * disabledActions: [Action.RenameModalTitleDescription] + * ``` + * @version SDK: 1.23.0 | ThoughtSpot: 9.4.0.cl, 9.8.0.sw + */ + RenameModalTitleDescription = "renameModalTitleDescription", + /** + * The *Request verification* action on a Liveboard. + * Initiates a request for Liveboard verification. + * @example + * ```js + * disabledActions: [Action.RequestVerification] + * ``` + * @version SDK: 1.25.0 | ThoughtSpot: 9.6.0.cl, 10.1.0.sw + */ + RequestVerification = "requestVerification", + /** + * + * Allows users to mark a Liveboard as verified. + * @example + * ```js + * disabledActions: [Action.MarkAsVerified] + * ``` + * @version SDK: 1.25.0 | ThoughtSpot: 9.6.0.cl, 10.1.0.sw + */ + MarkAsVerified = "markAsVerified", + /** + * The **Add Tab** action on a Liveboard. + * Allows adding a new tab to a Liveboard view. + * @example + * ```js + * disabledActions: [Action.AddTab] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + */ + AddTab = "addTab", + /** + * + * Initiates contextual change analysis on KPI charts. + * @example + * ```js + * disabledActions: [Action.EnableContextualChangeAnalysis] + * ``` + * @version SDK: 1.25.0 | ThoughtSpot Cloud: 9.6.0.cl + */ + EnableContextualChangeAnalysis = "enableContextualChangeAnalysis", + /** + * Action ID to hide or disable Iterative Change Analysis option + * in the contextual change analysis Insight charts context menu. + * + * @example + * ```js + * disabledActions: [Action.EnableIterativeChangeAnalysis] + * ``` + * @version SDK: 1.41.0 | ThoughtSpot Cloud: 9.12.0.cl + */ + EnableIterativeChangeAnalysis = "enableIterativeChangeAnalysis", + /** + * Action ID to hide or disable Natural Language Search query. + * + * @example + * ```js + * disabledActions: [Action.ShowSageQuery] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot Cloud: 9.7.0.cl + */ + ShowSageQuery = "showSageQuery", + /** + * + * Action ID to hide or disable the edit option for the + * results generated from the + * Natural Language Search query. + * + * @example + * ```js + * disabledActions: [Action.EditSageAnswer] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot Cloud: 9.7.0.cl + */ + EditSageAnswer = "editSageAnswer", + /** + * The feedback widget for AI-generated Answers. + * Allows users to send feedback on the Answers generated + * from a Natural Language Search query. + * + * @example + * ```js + * disabledActions: [Action.SageAnswerFeedback] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl + */ + SageAnswerFeedback = "sageAnswerFeedback", + /** + * + * @example + * ```js + * disabledActions: [Action.ModifySageAnswer] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl + */ + ModifySageAnswer = "modifySageAnswer", + /** + * The **Move to Tab** menu action on visualizations in Liveboard edit mode. + * Allows moving a visualization to a different tab. + * @example + * ```js + * disabledActions: [Action.MoveToTab] + * ``` + */ + MoveToTab = "onContainerMove", + /** + * The **Manage Alerts** menu action on KPI visualizations. + * Allows creating, viewing, and editing monitor + * alerts for a KPI chart. + * + * @example + * ```js + * disabledActions: [Action.ManageMonitor] + * ``` + */ + ManageMonitor = "manageMonitor", + /** + * The Liveboard Personalised Views dropdown. + * Allows navigating to a personalized Liveboard View. + * This action is deprecated. Use {@link Action.PersonalizedViewsDropdown} instead. + * @example + * ```js + * disabledActions: [Action.PersonalisedViewsDropdown] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 10.1.0.sw + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + PersonalisedViewsDropdown = "personalisedViewsDropdown", + /** + * The Liveboard Personalized Views dropdown. + * Allows navigating to a personalized Liveboard View. + * @example + * ```js + * disabledActions: [Action.PersonalizedViewsDropdown] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + PersonalizedViewsDropdown = "personalisedViewsDropdown", + /** + * Action ID for show or hide the user details on a + * Liveboard (Recently visited / social proof) + * @example + * ```js + * disabledActions: [Action.LiveboardUsers] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 10.1.0.sw + */ + LiveboardUsers = "liveboardUsers", + /** + * Action ID for the Parent TML action + * The parent action **TML** must be included to access TML-related options + * within the cascading menu (specific to the Answer page) + * @example + * ```js + * // to include specific TML actions + * visibleActions: [Action.TML, Action.ExportTML, Action.EditTML] + * + * ``` + * @example + * ```js + * hiddenAction: [Action.TML] // hide all TML actions + * disabledActions: [Action.TML] // to disable all TML actions + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: 9.12.0.cl, 10.1.0.sw + */ + TML = "tml", + /** + * The **Create Liveboard* action on + * the Liveboards page and the Pin modal. + * Allows users to create a Liveboard. + * + * @example + * ```js + * hiddenAction: [Action.CreateLiveboard] + * disabledActions: [Action.CreateLiveboard] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot: 10.1.0.cl, 10.1.0.sw + */ + CreateLiveboard = "createLiveboard", + /** + * Action ID for to hide or disable the + * Verified Liveboard banner. + * @example + * ```js + * hiddenAction: [Action.VerifiedLiveboard] + * ``` + * @version SDK: 1.29.0 | ThoughtSpot: 9.10.0.cl, 10.1.0.sw + */ + VerifiedLiveboard = "verifiedLiveboard", + /** + * Action ID for the *Ask Sage* In Natural Language Search embed, + * *Spotter* in Liveboard, full app, and Spotter embed. + * + * Allows initiating a conversation with ThoughtSpot AI analyst. + * + * @example + * ```js + * hiddenAction: [Action.AskAi] + * ``` + * @version SDK: 1.29.0 | ThoughtSpot Cloud: 9.12.0.cl + */ + AskAi = "AskAi", + /** + * The **Add KPI to Watchlist** action on Home page watchlist. + * Adds a KPI chart to the watchlist on the Home page. + * @example + * ```js + * disabledActions: [Action.AddToWatchlist] + * ``` + * @version SDK: 1.27.9 | ThoughtSpot Cloud: 9.12.5.cl + */ + AddToWatchlist = "addToWatchlist", + /** + * The **Remove from watchlist** menu action on KPI watchlist. + * Removes a KPI chart from the watchlist on the Home page. + * @example + * ```js + * disabledActions: [Action.RemoveFromWatchlist] + * ``` + * @version SDK: 1.27.9 | ThoughtSpot: 9.12.5.cl + */ + RemoveFromWatchlist = "removeFromWatchlist", + /** + * The **Organize Favourites** action on Homepage + * *Favorites* module. + * This action is deprecated. Use {@link Action.OrganizeFavorites} instead. + * + * @example + * ```js + * disabledActions: [Action.OrganiseFavourites] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + OrganiseFavourites = "organiseFavourites", + /** + * The **Organize Favorites** action on Homepage + * *Favorites* module. + * + * @example + * ```js + * disabledActions: [Action.OrganizeFavorites] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + OrganizeFavorites = "organiseFavourites", + /** + * The **AI Highlights** action on a Liveboard. + * + * @example + * ```js + * hiddenAction: [Action.AIHighlights] + * ``` + * @version SDK: 1.27.10 | ThoughtSpot Cloud: 9.12.5.cl + */ + AIHighlights = "AIHighlights", + /** + * The *Edit* action on the *Liveboard Schedules* page + * (new Homepage experience). + * Allows editing Liveboard schedules. + * + * @example + * ```js + * disabledActions: [Action.EditScheduleHomepage] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot Cloud: 10.3.0.cl + */ + EditScheduleHomepage = "editScheduleHomepage", + /** + * The *Pause* action on the *Liveboard Schedules* page + * Pauses a scheduled Liveboard job. + * @example + * ```js + * disabledActions: [Action.PauseScheduleHomepage] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot Cloud: 10.3.0.cl + */ + PauseScheduleHomepage = "pauseScheduleHomepage", + /** + * The **View run history** action **Liveboard Schedules** page. + * Allows viewing schedule run history. + * @example + * ```js + * disabledActions: [Action.ViewScheduleRunHomepage] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot: 10.3.0.cl + */ + ViewScheduleRunHomepage = "viewScheduleRunHomepage", + /** + * Action ID to hide or disable the + * unsubscribe option for Liveboard schedules. + * @example + * ```js + * disabledActions: [Action.UnsubscribeScheduleHomepage] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot: 10.3.0.cl + */ + UnsubscribeScheduleHomepage = "unsubscribeScheduleHomepage", + /** + * The **Manage Tags** action on Homepage Favourite Module. + * @example + * ```js + * disabledActions: [Action.ManageTags] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot Cloud: 10.3.0.cl + */ + ManageTags = "manageTags", + /** + * The **Delete** action on the **Liveboard Schedules* page. + * Deletes a Liveboard schedule. + * @example + * ```js + * disabledActions: [Action.DeleteScheduleHomepage] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot: 10.3.0.cl + */ + DeleteScheduleHomepage = "deleteScheduleHomepage", + /** + * The **Analyze CTA** action on KPI chart. + * @example + * ```js + * disabledActions: [Action.KPIAnalysisCTA] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot Cloud: 10.3.0.cl + */ + KPIAnalysisCTA = "kpiAnalysisCTA", + /** + * Action ID for disabling chip reorder in Answer and Liveboard + * @example + * ```js + * const disabledActions = [Action.DisableChipReorder] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + DisableChipReorder = "disableChipReorder", + /** + * Action ID to show, hide, or disable filters + * in a Liveboard tab. + * + * @example + * ```js + * hiddenAction: [Action.ChangeFilterVisibilityInTab] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + ChangeFilterVisibilityInTab = "changeFilterVisibilityInTab", + /** + * The **Data model instructions** button on the Spotter interface. + * Allows opening the data model instructions modal. + * + * @example + * ```js + * hiddenAction: [Action.DataModelInstructions] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + DataModelInstructions = "DataModelInstructions", + /** + * The **Preview data** button on the Spotter interface. + * Allows previewing the data used for Spotter queries. + * + * @example + * ```js + * hiddenAction: [Action.PreviewDataSpotter] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + PreviewDataSpotter = "previewDataSpotter", + /** + * The **Reset** link on the Spotter interface. + * Resets the conversation with Spotter. + * + * @example + * ```js + * hiddenAction: [Action.ResetSpotterChat] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + ResetSpotterChat = "resetSpotterChat", + /** + * Action ID for hide or disable the + * Spotter feedback widget. + * + * @example + * ```js + * hiddenAction: [Action.SpotterFeedback] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + SpotterFeedback = "spotterFeedback", + /** + * Action ID for hide or disable + * the previous prompt edit option in Spotter. + * + * @example + * ```js + * hiddenAction: [Action.EditPreviousPrompt] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + EditPreviousPrompt = "editPreviousPrompt", + /** + * Action ID for hide or disable + * the previous prompt deletion option in Spotter. + * + * @example + * ```js + * hiddenAction: [Action.DeletePreviousPrompt] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + DeletePreviousPrompt = "deletePreviousPrompt", + /** + * Action ID for hide or disable editing tokens generated from + * Spotter results. + * @example + * ```js + * hiddenAction: [Action.EditTokens] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + EditTokens = "editTokens", + /** + * Action ID for hiding rename option for Column rename + * @example + * ```js + * hiddenAction: [Action.ColumnRename] + * ``` + * @version SDK: 1.37.0 | ThoughtSpot Cloud: 10.8.0.cl + */ + ColumnRename = "columnRename", + /** + * Action ID for hide checkboxes for include or exclude + * cover and filter pages in the Liveboard PDF + * @example + * ```js + * hiddenAction: [Action.CoverAndFilterOptionInPDF] + * ``` + * @version SDK: 1.37.0 | ThoughtSpot Cloud: 10.8.0.cl + */ + CoverAndFilterOptionInPDF = "coverAndFilterOptionInPDF", + /** + * Action ID to hide or disable the Coaching workflow in Spotter conversations. + * When disabled, users cannot access **Add to Coaching** workflow in conversation. + * The **Add to Coaching** feature allows adding reference questions and + * business terms to improve Spotter’s responses. This feature is generally available + * (GA) from version 26.2.0.cl and enabled by default on embed deployments. + * @example + * ```js + * hiddenAction: [Action.InConversationTraining] + * disabledActions: [Action.InConversationTraining] + * + * ``` + * @version SDK: 1.39.0 | ThoughtSpot Cloud: 10.10.0.cl + */ + InConversationTraining = "InConversationTraining", + /** + * Action ID to hide the warnings banner in + * Spotter results. It's an EA feature and + * handled by LD. + * @example + * ```js + * hiddenAction: [Action.SpotterWarningsBanner] + * ``` + * @version SDK: 1.41.0 | ThoughtSpot Cloud: 10.13.0.cl + */ + SpotterWarningsBanner = "SpotterWarningsBanner", + /** + * Action ID to hide the warnings border on the knowledge + * card in Spotter results. It's an EA feature and + * handled by LD. + * @example + * ```js + * hiddenAction: [Action.SpotterWarningsOnTokens] + * ``` + * @version SDK: 1.41.0 | ThoughtSpot Cloud: 10.13.0.cl + */ + SpotterWarningsOnTokens = "SpotterWarningsOnTokens", + /** + * Action ID to disable the click event handler on knowledge + * card in Spotter results. It's an EA feature and + * handled by LD. + * @example + * ```js + * hiddenAction: [Action.SpotterTokenQuickEdit] + * ``` + * @version SDK: 1.41.0 | ThoughtSpot Cloud: 10.13.0.cl + */ + SpotterTokenQuickEdit = "SpotterTokenQuickEdit", + /** + * The **PNG screenshot in email** option in the schedule email dialog. + * Includes a PNG screenshot in the notification email body. + * @example + * ```js + * disabledActions: [Action.PngScreenshotInEmail] + * ``` + * ``` + * @version SDK: 1.42.0 | ThoughtSpot Cloud: 10.14.0.cl + */ + PngScreenshotInEmail = "pngScreenshotInEmail", + /** + * The **Remove attachment** action in the schedule email dialog. + * Removes an attachment from the email configuration. + * @example + * ```js + * disabledActions: [Action.RemoveAttachment] + * ``` + * ``` + * ``` + * @version SDK: 1.42.0 | ThoughtSpot Cloud: 10.14.0.cl + */ + RemoveAttachment = "removeAttachment", + /** + * The **Style panel** on a Liveboard. + * Controls the visibility of the Liveboard style panel. + * @example + * ```js + * hiddenActions: [Action.LiveboardStylePanel] + * ``` + * @version SDK: 1.43.0 | ThoughtSpot Cloud: 10.15.0.cl + */ + LiveboardStylePanel = "liveboardStylePanel", + /** + * The **Publish** action for Liveboards, Answers and Models. + * Opens the publishing modal. It's a parent action for the + * **Manage Publishing** and **Unpublish** actions if the object + * is already published, otherwise appears standalone. + * @example + * ```js + * hiddenActions: [Action.Publish] + * disabledActions: [Action.Publish] + * ``` + * @version SDK: 1.45.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Publish = "publish", + /** + * The **Manage Publishing** action for Liveboards, Answers and Models. + * Opens the same publishing modal as the **Publish** action. + * Appears as a child action to the **Publish** action if the + * object is already published. + * @example + * ```js + * hiddenActions: [Action.ManagePublishing] + * disabledActions: [Action.ManagePublishing] + * ``` + * @version SDK: 1.45.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + ManagePublishing = "managePublishing", + /** + * The **Unpublish** action for Liveboards, Answers and Models. + * Opens the unpublishing modal. Appears as a child action to + * the **Publish** action if the object is already published. + * @example + * ```js + * hiddenActions: [Action.Unpublish] + * disabledActions: [Action.Unpublish] + * ``` + * @version SDK: 1.45.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Unpublish = "unpublish", + /** + * The **Parameterize** action for Tables and Connections. + * Opens the parameterization modal. + * @example + * ```js + * hiddenActions: [Action.Parameterize] + * disabledActions: [Action.Parameterize] + * ``` + * @version SDK: 1.45.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Parameterize = "parameterise", + /** + * The **Move to Group** menu action on a Liveboard. + * Allows moving a visualization to a different group. + * @example + * ```js + * disabledActions: [Action.MoveToGroup] + * ``` + * @version SDK: 1.44.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + MoveToGroup = "moveToGroup", + /** + * The **Move out of Group** menu action on a Liveboard. + * Allows moving a visualization out of a group. + * @example + * ```js + * disabledActions: [Action.MoveOutOfGroup] + * ``` + * @version SDK: 1.44.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + MoveOutOfGroup = "moveOutOfGroup", + /** + * The **Create Group** menu action on a Liveboard. + * Allows creating a new group. + * @example + * ```js + * disabledActions: [Action.CreateGroup] + * ``` + * @version SDK: 1.44.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + CreateGroup = "createGroup", + /** + * The **Ungroup Liveboard Group** menu action on a Liveboard. + * Allows ungrouping a liveboard group. + * @example + * ```js + * disabledActions: [Action.UngroupLiveboardGroup] + * ``` + * @version SDK: 1.44.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + UngroupLiveboardGroup = "ungroupLiveboardGroup", + /** + * Controls visibility of the sidebar header (title and toggle button) + * in the Spotter past conversations sidebar. + * @example + * ```js + * hiddenActions: [Action.SpotterSidebarHeader] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + SpotterSidebarHeader = "spotterSidebarHeader", + /** + * Controls visibility of the sidebar footer (documentation link) + * in the Spotter past conversations sidebar. + * @example + * ```js + * hiddenActions: [Action.SpotterSidebarFooter] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + SpotterSidebarFooter = "spotterSidebarFooter", + /** + * Controls visibility and disable state of the sidebar toggle/expand button + * in the Spotter past conversations sidebar. + * @example + * ```js + * disabledActions: [Action.SpotterSidebarToggle] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + SpotterSidebarToggle = "spotterSidebarToggle", + /** + * Controls visibility and disable state of the "New Chat" button + * in the Spotter past conversations sidebar. + * @example + * ```js + * disabledActions: [Action.SpotterNewChat] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + SpotterNewChat = "spotterNewChat", + /** + * Controls visibility of the past conversation banner alert + * in the Spotter interface. + * @example + * ```js + * hiddenActions: [Action.SpotterPastChatBanner] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + SpotterPastChatBanner = "spotterPastChatBanner", + /** + * Controls visibility and disable state of the conversation edit menu + * (three-dot menu) in the Spotter past conversations sidebar. + * @example + * ```js + * disabledActions: [Action.SpotterChatMenu] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + SpotterChatMenu = "spotterChatMenu", + /** + * Controls visibility and disable state of the rename action + * in the Spotter conversation edit menu. + * @example + * ```js + * disabledActions: [Action.SpotterChatRename] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + SpotterChatRename = "spotterChatRename", + /** + * Controls visibility and disable state of the delete action + * in the Spotter conversation edit menu. + * @example + * ```js + * disabledActions: [Action.SpotterChatDelete] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + SpotterChatDelete = "spotterChatDelete", + /** + * Controls visibility and disable state of the documentation/best practices + * link in the Spotter sidebar footer. + * @example + * ```js + * disabledActions: [Action.SpotterDocs] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + SpotterDocs = "spotterDocs", + /** + * Controls visibility and disable state of the connector resources + * section in the Spotter chat interface. + * @example + * ```js + * hiddenActions: [Action.SpotterChatConnectorResources] + * disabledActions: [Action.SpotterChatConnectorResources] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot Cloud: 26.5.0.cl + */ + SpotterChatConnectorResources = "spotterChatConnectorResources", + /** + * Controls visibility and disable state of the connectors + * in the Spotter chat interface. + * @example + * ```js + * hiddenActions: [Action.SpotterChatConnectors] + * disabledActions: [Action.SpotterChatConnectors] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot Cloud: 26.5.0.cl + */ + SpotterChatConnectors = "spotterChatConnectors", + /** + * Controls visibility and disable state of the mode switcher + * in the Spotter chat interface. + * @example + * ```js + * hiddenActions: [Action.SpotterChatModeSwitcher] + * disabledActions: [Action.SpotterChatModeSwitcher] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot Cloud: 26.5.0.cl + */ + SpotterChatModeSwitcher = "spotterChatModeSwitcher", + /** + * The **Include current period** checkbox for date filters. + * Controls the visibility and availability of the option to include + * the current time period in filter results. + * @example + * ```js + * hiddenActions: [Action.IncludeCurrentPeriod] + * disabledActions: [Action.IncludeCurrentPeriod] + * ``` + * @version SDK: 1.45.0 | ThoughtSpot: 26.4.0.cl + */ + IncludeCurrentPeriod = "includeCurrentPeriod", + /** + * The **Send Test Email** button in the Liveboard schedule modal. + * Allows sending a test schedule email to self or all recipients. + * Requires `isSendNowLiveboardSchedulingEnabled` to be enabled. + * @example + * ```js + * disabledActions: [Action.SendTestScheduleEmail] + * hiddenActions: [Action.SendTestScheduleEmail] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot Cloud: 26.5.0.cl + */ + SendTestScheduleEmail = "sendTestScheduleEmail", + /** + * The thumbs up/down feedback buttons in the SpotterViz panel. + * Visible by default. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * hiddenActions: [Action.SpotterVizFeedback] + * disabledActions: [Action.SpotterVizFeedback] + * ``` + */ + SpotterVizFeedback = "spotterVizFeedback", + /** + * The version restore button on checkpoint cards in the SpotterViz panel. + * Visible by default. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * hiddenActions: [Action.SpotterVizCheckpointRestore] + * disabledActions: [Action.SpotterVizCheckpointRestore] + * ``` + */ + SpotterVizCheckpointRestore = "spotterVizCheckpointRestore", + /** + * The **SpotterViz** button in the top edit header. + * Visible by default. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * hiddenActions: [Action.SpotterViz] + * disabledActions: [Action.SpotterViz] + * ``` + */ + SpotterViz = "spotterViz", + /** + * Clears browser cache and fetches new data for liveboard ChartViz Containers. + * Requires `enableLiveboardDataCache` to be enabled. + * @example + * ```js + * disabledActions: [Action.RefreshLiveboardBrowserCache] + * hiddenActions: [Action.RefreshLiveboardBrowserCache] + * ``` + * @version SDK: 1.49.0 | ThoughtSpot Cloud: 26.6.0.cl + */ + RefreshLiveboardBrowserCache = "refreshLiveboardBrowserCache" +} +export interface AnswerServiceType { + getAnswer?: (offset: number, batchSize: number) => any; +} +export declare enum PrefetchFeatures { + FullApp = "FullApp", + SearchEmbed = "SearchEmbed", + LiveboardEmbed = "LiveboardEmbed", + VizEmbed = "VizEmbed" +} +/** + * Enum for options to change context trigger. + * The `BOTH_CLICKS` option is available from 10.8.0.cl. + */ +export declare enum ContextMenuTriggerOptions { + LEFT_CLICK = "left-click", + RIGHT_CLICK = "right-click", + BOTH_CLICKS = "both-clicks" +} +export interface ColumnValue { + column: { + id: string; + name: string; + dataType: string; + [key: string]: any; + }; + value: string | number | boolean | { + v: { + s: number; + e: number; + }; + }; +} +export interface VizPoint { + selectedAttributes: ColumnValue[]; + selectedMeasures: ColumnValue[]; +} +/** + * @group Events + */ +export interface CustomActionPayload { + contextMenuPoints?: { + clickedPoint: VizPoint; + selectedPoints: VizPoint[]; + }; + embedAnswerData: { + name: string; + id: string; + sources: { + header: { + guid: string; + }; + }; + columns: any[]; + data: any[]; + [key: string]: any; + }; + session: SessionInterface; + vizId?: string; +} +export interface CustomAction { + name: string; + id: string; + position: CustomActionsPosition; + target: CustomActionTarget; + metadataIds?: { + answerIds?: string[]; + liveboardIds?: string[]; + vizIds?: string[]; + }; + dataModelIds?: { + modelIds?: string[]; + modelColumnNames?: string[]; + }; + orgIds?: string[]; + groupIds?: string[]; +} +/** + * Enum options to show custom actions at different + * positions in the application. + */ +export declare enum CustomActionsPosition { + /** + * Shows the action as a primary button + * in the toolbar area of the embed. + */ + PRIMARY = "PRIMARY", + /** + * Shows the action inside the "More" menu + * (three-dot overflow menu). + */ + MENU = "MENU", + /** + * Shows the action in the right-click + * context menu. Only supported for + * {@link CustomActionTarget.VIZ}, + * {@link CustomActionTarget.ANSWER}, and + * {@link CustomActionTarget.SPOTTER} targets. + */ + CONTEXTMENU = "CONTEXTMENU" +} +/** + * Enum options to mention the target of the code-based custom action. + * The target determines which type of ThoughtSpot object the action is + * associated with, and also controls which positions and scoping options + * are available. + */ +export declare enum CustomActionTarget { + /** + * Action applies at the Liveboard level. + * Supported positions: + * {@link CustomActionsPosition.PRIMARY}, + * {@link CustomActionsPosition.MENU}. + * Can be scoped with + * `metadataIds.liveboardIds`, + * `orgIds`, and `groupIds`. + */ + LIVEBOARD = "LIVEBOARD", + /** + * Action applies to individual + * visualizations (charts/tables). + * Supported positions: + * {@link CustomActionsPosition.PRIMARY}, + * {@link CustomActionsPosition.MENU}, + * {@link CustomActionsPosition.CONTEXTMENU}. + * Can be scoped with `metadataIds` + * (answerIds, liveboardIds, vizIds), + * `dataModelIds` (modelIds, + * modelColumnNames), `orgIds`, + * and `groupIds`. + */ + VIZ = "VIZ", + /** + * Action applies to saved or unsaved + * Answers. Supported positions: + * {@link CustomActionsPosition.PRIMARY}, + * {@link CustomActionsPosition.MENU}, + * {@link CustomActionsPosition.CONTEXTMENU}. + * Can be scoped with + * `metadataIds.answerIds`, + * `dataModelIds` (modelIds, + * modelColumnNames), `orgIds`, + * and `groupIds`. + */ + ANSWER = "ANSWER", + /** + * Action applies to Spotter + * (AI-powered search). + * Supported positions: + * {@link CustomActionsPosition.MENU}, + * {@link CustomActionsPosition.CONTEXTMENU}. + * Can be scoped with + * `dataModelIds.modelIds`, + * `orgIds`, and `groupIds`. + */ + SPOTTER = "SPOTTER" +} +/** + * Enum options to show or suppress Visual Embed SDK and + * ThoughtSpot application logs in the console output. + * This attribute doesn't support suppressing + * browser warnings or errors. + */ +export declare enum LogLevel { + /** + * No application or SDK-related logs will be logged + * in the console output. + * @example + * ```js + * init({ + * ... //other embed view config, + * logLevel: LogLevel.SILENT, + * }) + * ``` + * @version SDK: 1.26.7 | ThoughtSpot Cloud: 9.10.0.cl + */ + SILENT = "SILENT", + /** + * Log only errors in the console output. + * @example + * ```js + * init({ + * ... //other embed view config, + * logLevel: LogLevel.ERROR, + * }) + * ``` + * @version SDK: 1.26.7 | ThoughtSpot Cloud: 9.10.0.cl + */ + ERROR = "ERROR", + /** + * Log only warnings and errors in the console output. + * @example + * ```js + * init({ + * ... //other embed view config, + * logLevel: LogLevel.WARN, + * }) + * ``` + * @version SDK: 1.26.7 | ThoughtSpot Cloud: 9.10.0.cl + */ + WARN = "WARN", + /** + * Log only the information alerts, warnings, and errors + * in the console output. + * @example + * ```js + * init({ + * ... //other embed view config, + * logLevel: LogLevel.INFO, + * }) + * ``` + * @version SDK: 1.26.7 | ThoughtSpot Cloud: 9.10.0.cl + */ + INFO = "INFO", + /** + * Log debug messages, warnings, information alerts, + * and errors in the console output. + * @example + * ```js + * init({ + * ... //other embed view config, + * logLevel: LogLevel.DEBUG, + * }) + * ``` + * @version SDK: 1.26.7 | ThoughtSpot Cloud: 9.10.0.cl + */ + DEBUG = "DEBUG", + /** + * All logs will be logged in the browser console. + * @example + * ```js + * init({ + * ... //other embed view config, + * logLevel: LogLevel.TRACE, + * }) + * ``` + * @version SDK: 1.26.7 | ThoughtSpot Cloud: 9.10.0.cl + */ + TRACE = "TRACE" +} +/** + * Error types emitted by embedded components. + * + * These enum values categorize different types of errors that can occur during + * the lifecycle of an embedded ThoughtSpot component. + * Use {@link EmbedErrorDetailsEvent} and {@link EmbedErrorCodes} to handle specific errors. + * @version SDK: 1.44.2 | ThoughtSpot: 26.2.0.cl + * @group Error Handling + * + * @example + * Handle specific error types + * ```js + * embed.on(EmbedEvent.Error, (error) => { + * switch (error.errorType) { + * case ErrorDetailsTypes.API: + * console.error('API error:', error.message); + * break; + * case ErrorDetailsTypes.VALIDATION_ERROR: + * console.error('Validation error:', error.message); + * break; + * case ErrorDetailsTypes.NETWORK: + * console.error('Network error:', error.message); + * break; + * default: + * console.error('Unknown error:', error); + * } + * }); + * ``` + */ +export declare enum ErrorDetailsTypes { + /** API call failure */ + API = "API", + /** General validation error */ + VALIDATION_ERROR = "VALIDATION_ERROR", + /** Network connectivity or request error */ + NETWORK = "NETWORK" +} +/** + * Error codes for identifying specific issues in embedded ThoughtSpot components. Use + * {@link EmbedErrorDetailsEvent} and {@link ErrorDetailsTypes} codes for precise error + * handling and debugging. + * + * @version SDK: 1.44.2 | ThoughtSpot: 26.2.0.cl + * @group Error Handling + + * @example + * Handle specific error codes in the error event handler + * ```js + * embed.on(EmbedEvent.Error, (error) => { + * switch (error.code) { + * case EmbedErrorCodes.WORKSHEET_ID_NOT_FOUND: + * console.error('Worksheet ID not found:', error.message); + * break; + * case EmbedErrorCodes.LIVEBOARD_ID_MISSING: + * console.error('Liveboard ID is missing:', error.message); + * break; + * case EmbedErrorCodes.CONFLICTING_ACTIONS_CONFIG: + * console.error('Conflicting actions configuration:', error.message); + * break; + * case EmbedErrorCodes.CONFLICTING_TABS_CONFIG: + * console.error('Conflicting tabs configuration:', error.message); + * break; + * default: + * console.error('Unknown error:', error); + * } + * }); + * ``` + * */ +export declare enum EmbedErrorCodes { + /** Worksheet ID not found or does not exist */ + WORKSHEET_ID_NOT_FOUND = "WORKSHEET_ID_NOT_FOUND", + /** Required Liveboard ID is missing from configuration */ + LIVEBOARD_ID_MISSING = "LIVEBOARD_ID_MISSING", + /** Conflicting action configuration detected */ + CONFLICTING_ACTIONS_CONFIG = "CONFLICTING_ACTIONS_CONFIG", + /** Conflicting tab configuration detected */ + CONFLICTING_TABS_CONFIG = "CONFLICTING_TABS_CONFIG", + /** Error during component initialization */ + INIT_ERROR = "INIT_ERROR", + /** Network connectivity or request error */ + NETWORK_ERROR = "NETWORK_ERROR", + /** Custom action validation failed */ + CUSTOM_ACTION_VALIDATION = "CUSTOM_ACTION_VALIDATION", + /** Authentication/login failed */ + LOGIN_FAILED = "LOGIN_FAILED", + /** Render method was not called before attempting to use the component */ + RENDER_NOT_CALLED = "RENDER_NOT_CALLED", + /** Host event type is undefined or invalid */ + HOST_EVENT_TYPE_UNDEFINED = "HOST_EVENT_TYPE_UNDEFINED", + /** Error parsing api intercept body */ + PARSING_API_INTERCEPT_BODY_ERROR = "PARSING_API_INTERCEPT_BODY_ERROR", + /** Failed to update embed parameters during pre-render */ + UPDATE_PARAMS_FAILED = "UPDATE_PARAMS_FAILED", + /** Invalid URL provided in configuration */ + INVALID_URL = "INVALID_URL", + /** Host event payload validation failed */ + HOST_EVENT_VALIDATION = "HOST_EVENT_VALIDATION", + /** UpdateFilters payload is invalid - missing or malformed filter/filters */ + UPDATEFILTERS_INVALID_PAYLOAD = "UPDATEFILTERS_INVALID_PAYLOAD", + /** DrillDown payload is invalid - missing or malformed points */ + DRILLDOWN_INVALID_PAYLOAD = "DRILLDOWN_INVALID_PAYLOAD" +} +/** + * Error event object emitted when an error occurs in an embedded component. + * + * This interface defines the structure of error objects returned by the {@link + * EmbedEvent.Error} event. It provides detailed information about what went wrong, + * including the error type, a human-readable message, and a machine-readable error code. + * + * ## Properties + * + * - **errorType**: One of the predefined {@link ErrorDetailsTypes} values + * - **message**: Human-readable error description (string or array of strings for + * multiple errors) + * - **code**: Machine-readable error identifier {@link EmbedErrorCodes} + * values + * - **[key: string]**: Additional context-specific for backward compatibility + * + * ## Usage + * + * Listen to the {@link EmbedEvent.Error} event to receive instances of this object + * and implement appropriate error handling logic based on the `errorType`. + * + * @version SDK: 1.44.2 | ThoughtSpot: 26.2.0.cl + * @group Error Handling + * + * @example + * Handle specific error types + * + * ```js + * embed.on(EmbedEvent.Error, (error) => { + * switch (error.code) { + * case EmbedErrorCodes.WORKSHEET_ID_NOT_FOUND: + * console.error('Worksheet ID not found:', error.message, error.code); + * break; + * default: + * console.error('Unknown error:', error); + * } + * }); + * ``` + * @example + * Handle multiple error messages + * + * ```js + * embed.on(EmbedEvent.Error, (error) => { + * const messages = Array.isArray(error.message) + * ? error.message + * : [error.message]; + * messages.forEach(msg => console.error(msg)); + * }); + * ``` + * */ +export interface EmbedErrorDetailsEvent { + /** The type of error that occurred */ + errorType: ErrorDetailsTypes; + /** Human-readable error message(s) describing what went wrong */ + message: string | string[]; + /** Machine-readable error code for programmatic error handling */ + code: EmbedErrorCodes; + /** Additional context-specific for backward compatibility */ + [key: string]: any; +} +/** + * Context types for specifying the page context when triggering host events. + * Used as the third parameter in the `trigger` method to help ThoughtSpot + * understand the current page context for better event handling. + * + * @example + * ```js + * import { HostEvent, ContextType } from '@thoughtspot/visual-embed-sdk'; + * + * // Trigger an event with specific context + * embed.trigger(HostEvent.Pin, { vizId: "123", liveboardId: "456" }, ContextType.Search); + * ``` + * + * @version SDK: 1.45.2 | ThoughtSpot: 26.3.0.cl + * @group Events + */ +export declare enum ContextType { + /** + * Search answer context for search page or edit viz dialog on liveboard page. + */ + Search = "search-answer", + /** + * Liveboard context for liveboard page. + */ + Liveboard = "liveboard", + /** + * Answer context for explore modal/page on liveboard page. + */ + Answer = "answer", + /** + * Spotter context for spotter modal/page. + */ + Spotter = "spotter", + /** + * Other context for any other generic page. + */ + Other = "other" +} +export interface DefaultAppInitData { + customisations: CustomisationsInterface; + authToken: string; + runtimeFilterParams: string | null; + runtimeParameterParams: string | null; + hiddenHomepageModules: HomepageModule[]; + reorderedHomepageModules: string[]; + hostConfig: Record; + hiddenHomeLeftNavItems: string[]; + customVariablesForThirdPartyTools: Record; + hiddenListColumns: ListPageColumns[]; + customActions: CustomAction[]; + interceptTimeout: number | undefined; + interceptUrls: (string | InterceptedApiType)[]; + embedExpiryInAuthToken: boolean; + shouldBypassPayloadValidation?: boolean; + useHostEventsV2?: boolean; +} +/** + * Enum for the type of API intercepted + */ +export declare enum InterceptedApiType { + /** + * The apis that are use to get the data for the embed + */ + AnswerData = "AnswerData", + /** + * This will intercept all the apis + */ + ALL = "ALL", + /** + * The apis that are use to get the data for the liveboard + */ + LiveboardData = "LiveboardData" +} +export type ApiInterceptFlags = { + /** + * Flag that allows using `EmbedEvent.OnBeforeGetVizDataIntercept`. + * + * Can be used for Search and App Embed from SDK 1.29.0 + * + * @version SDK: 1.43.0 | ThoughtSpot: 10.15.0.cl + */ + isOnBeforeGetVizDataInterceptEnabled?: boolean; + /** + * This allows to intercept the urls passed, once intercepted the api will only + * run based on the response from the responder of ApiIntercept event. + * + * @example + * ```js + * const embed = new LiveboardEmbed('#embed', { + * ...viewConfig, + * enableApiIntercept: true, + * interceptUrls: [InterceptedApiType.DATA], + * }) + * ``` + * + * @version SDK: 1.43.0 | ThoughtSpot: 10.15.0.cl + */ + interceptUrls?: (string | InterceptedApiType)[]; + /** + * The timeout for the intercept, default is 30000ms + * the api will error out if the timeout is reached + * + * @example + * ```js + * const embed = new LiveboardEmbed('#embed', { + * ...viewConfig, + * enableApiIntercept: true, + * interceptUrls: [InterceptedApiType.ALL], + * interceptTimeout: 1000, + * }) + * ``` + * + * @version SDK: 1.43.0 | ThoughtSpot: 10.15.0.cl + */ + interceptTimeout?: number; +}; +/** + * Object IDs for the embedded component. + * @version SDK: 1.45.2 | ThoughtSpot: 26.3.0.cl + */ +export interface ObjectIds { + /** + * Liveboard ID. + */ + liveboardId?: string; + /** + * Answer ID. + */ + answerId?: string; + /** + * Viz IDs. + */ + vizIds?: string[]; + /** + * Data model IDs. + */ + dataModelIds?: string[]; + /** + * Modal title. + */ + modalTitle?: string; +} +export interface ContextObject { + /** + * Stack of context objects. + */ + stack: Array<{ + /** + * Name of the context object. + */ + name: string; + /** + * Type of the context object. + */ + type: ContextType; + /** + * Object IDs of the context object. + */ + objectIds: ObjectIds; + }>; + /** + * Current context object. + */ + currentContext: { + /** + * Name of the current context object. + */ + name: string; + type: ContextType; + objectIds: ObjectIds; + }; +} +export interface FontProperties { + color?: string; + bold?: boolean; + italic?: boolean; + strikeThrough?: boolean; + underline?: boolean; +} +export interface SolidBackgroundAttrs { + color?: string; +} +export interface GradientBackgroundAttrs { + backgroundFormatMidpoint?: number; + colors?: string[]; + backgroundFormatRange?: number[]; + isAutoScaled?: boolean; +} +/** + * Data label filter operators + * @group Visual Overrides + */ +export declare enum DataLabelFilterOperator { + /** Greater than */ + GreaterThan = "GREATER_THAN", + /** Less than */ + LessThan = "LESS_THAN", + /** Greater than or equal to */ + GreaterThanOrEqualTo = "GREATER_THAN_OR_EQUAL_TO", + /** Less than or equal to */ + LessThanOrEqualTo = "LESS_THAN_OR_EQUAL_TO", + /** Equal to */ + EqualTo = "EQUAL_TO", + /** Not equal to */ + NotEqualTo = "NOT_EQUAL_TO" +} +/** + * Conditional formatting operators + * @group Visual Overrides + */ +export declare enum ConditionalFormattingOperator { + /** Is equal to */ + Is = "IS", + /** Is not equal to */ + IsNot = "IS_NOT", + /** Contains */ + Contains = "CONTAINS", + /** Does not contain */ + DoesNotContain = "DOES_NOT_CONTAIN", + /** Starts with */ + StartsWith = "STARTS_WITH", + /** Ends with */ + EndsWith = "ENDS_WITH", + /** Greater than */ + GreaterThan = "GREATER_THAN", + /** Less than */ + LessThan = "LESS_THAN", + /** Greater than or equal to */ + GreaterThanEqualTo = "GREATER_THAN_EQUAL_TO", + /** Less than or equal to */ + LessThanEqualTo = "LESS_THAN_EQUAL_TO", + /** Equal to */ + EqualTo = "EQUAL_TO", + /** Not equal to */ + NotEqualTo = "NOT_EQUAL_TO", + /** Is between */ + IsBetween = "IS_BETWEEN", + /** Is null */ + IsNull = "IS_NULL", + /** Is not null */ + IsNotNull = "IS_NOT_NULL" +} +/** + * Background format types for conditional formatting + * @group Visual Overrides + */ +export declare enum BackgroundFormatType { + /** Solid color background */ + Solid = "SOLID", + /** Gradient background */ + Gradient = "GRADIENT" +} +/** + * Comparison types for conditional formatting + * @group Visual Overrides + */ +export declare enum ConditionalFormattingComparisonType { + /** Value-based comparison */ + ValueBased = "VALUE_BASED", + /** Column-based comparison */ + ColumnBased = "COLUMN_BASED", + /** Parameter-based comparison */ + ParameterBased = "PARAMETER_BASED" +} +/** + * A single conditional formatting rule row + * @group Visual Overrides + */ +export interface ConditionalFormattingRow { + /** Comparison operator */ + operator: ConditionalFormattingOperator | string; + /** Value to compare against */ + value?: string; + /** Range values for range-based comparisons */ + rangeValues?: { + min: number; + max: number; + }; + /** Plot the formatting as a band/area */ + plotAsBand?: boolean; + /** Highlight this row if the condition is met */ + isHighlightRow?: boolean; + /** Type of comparison: value-based, column-based, or parameter-based */ + comparisonType?: ConditionalFormattingComparisonType | string; + /** Column ID to apply the formatting to (left-hand side) */ + lhsColumnId?: string; + /** Column name to compare against (right-hand side) */ + columnToCompare?: string; + /** Parameter ID to compare against */ + comparisonParameterId?: string; + /** Font properties to apply (color, bold, italic, etc.) */ + fontProperties?: FontProperties; + /** Background format type: solid color or gradient */ + backgroundFormatType?: BackgroundFormatType | string; + /** Solid background color attributes */ + solidBackgroundAttrs?: SolidBackgroundAttrs; + /** Gradient background attributes */ + gradientBackgroundAttrs?: GradientBackgroundAttrs; +} +/** + * Conditional formatting configuration + * @group Visual Overrides + */ +export interface ConditionalFormatting { + /** Array of conditional formatting rules */ + rows?: ConditionalFormattingRow[]; +} +/** + * Color palette for charts + * @group Visual Overrides + */ +export interface ColorPalette { + /** Array of color values (hex codes or color names) */ + colors?: string[]; +} +/** + * Legend position options + * @group Visual Overrides + */ +export declare enum LegendPosition { + /** Position legend at the top */ + Top = "top", + /** Position legend at the bottom */ + Bottom = "bottom", + /** Position legend on the left */ + Left = "left", + /** Position legend on the right */ + Right = "right" +} +/** + * Table theme options + * @group Visual Overrides + */ +export declare enum TableTheme { + /** Outline theme */ + Outline = "OUTLINE", + /** Row theme */ + Row = "ROW", + /** Zebra theme */ + Zebra = "ZEBRA" +} +/** + * Table content density options + * @group Visual Overrides + */ +export declare enum TableContentDensity { + /** Regular density */ + Regular = "REGULAR", + /** Compact density */ + Compact = "COMPACT" +} +/** + * Chart legend configuration + * @group Visual Overrides + */ +export interface ChartLegend { + /** Show or hide the legend */ + show?: boolean; + /** Position of the legend */ + position?: LegendPosition | string; + /** Color palette to use for legend colors */ + colorPalette?: ColorPalette; +} +/** + * Filter for data labels + * @group Visual Overrides + */ +export interface DataLabelFilter { + /** Filter threshold value */ + value?: number; + /** Filter operator */ + operator?: DataLabelFilterOperator | string; +} +/** + * Data label configuration for a specific column + * @group Visual Overrides + */ +export interface ColumnDataLabel { + /** Column name to apply data label overrides to */ + name: string; + /** Show or hide data labels for this column */ + visible?: boolean; + /** Filter to apply to data labels */ + filter?: DataLabelFilter | null; +} +/** + * Chart data label configuration + * @group Visual Overrides + */ +export interface ChartDataLabel { + /** Show labels for all data points */ + allLabels?: boolean; + /** Show labels for stacked values */ + stackLabels?: boolean; + /** Per-column data label configurations */ + columnDataLabel?: ColumnDataLabel[]; +} +/** + * Chart summaries and totals configuration + * @group Visual Overrides + */ +export interface ChartSummaries { + /** Show row totals */ + showRowTotals?: boolean; + /** Show column totals */ + showColumnTotals?: boolean; + /** Show row grand totals */ + showRowGrandTotals?: boolean; + /** Show column grand totals */ + showColumnGrandTotals?: boolean; +} +/** + * Gridline configuration + * @group Visual Overrides + */ +export interface GridLine { + /** Show vertical gridlines */ + x?: boolean; + /** Show horizontal gridlines */ + y?: boolean; +} +/** + * Chart display configuration + * @group Visual Overrides + */ +export interface ChartDisplay { + /** Summary and totals configuration */ + summaries?: ChartSummaries; + /** Show regression line on chart */ + regressionLine?: boolean; + /** Gridline visibility configuration */ + gridLine?: GridLine; +} +/** + * Y-axis range configuration + * @group Visual Overrides + */ +export interface YAxisRange { + /** Minimum value for Y-axis */ + min?: number; + /** Maximum value for Y-axis */ + max?: number; +} +/** + * Chart axis configuration + * @group Visual Overrides + */ +export interface ChartAxis { + /** Column names to link to this axis */ + linkedColumns?: string[]; + /** Show the axis name */ + showName?: boolean; + /** Show the axis label values */ + showLabelValue?: boolean; + /** Y-axis range configuration */ + yAxisRange?: YAxisRange; +} +/** + * Chart column override configuration + * @group Visual Overrides + */ +export interface ChartColumn { + /** Column name to apply overrides to */ + name: string; + /** Color for the column (hex code) */ + color?: string; + /** Conditional formatting rules to apply to the column */ + conditionalFormatting?: ConditionalFormatting; +} +/** + * Chart visualization overrides + * @group Visual Overrides + */ +export interface ChartOverrides { + /** Legend configuration */ + legend?: ChartLegend; + /** Data label configuration */ + dataLabel?: ChartDataLabel; + /** Display properties (summaries, regression line, gridlines) */ + display?: ChartDisplay; + /** Per-axis configurations */ + axis?: ChartAxis[]; + /** Per-column configurations */ + columns?: ChartColumn[]; + /** Update mask paths for partial updates */ + updateMaskPaths?: string[]; +} +/** + * Table column override configuration + * @group Visual Overrides + */ +export interface TableColumn { + /** + * Name of the column to apply overrides to + */ + name: string; + /** + * Enable or disable text wrapping for the column + */ + wrapText?: boolean; + /** + * Show or hide the column + */ + show?: boolean; + /** + * Conditional formatting rules to apply to the column + */ + conditionalFormatting?: ConditionalFormatting; +} +/** + * Table display configuration + * @group Visual Overrides + */ +export interface TableDisplay { + /** Table theme */ + tableTheme?: TableTheme | string; + /** Table content density */ + tableContentDensity?: TableContentDensity | string; +} +/** + * Column summary visibility configuration + * @group Visual Overrides + */ +export interface ColumnSummaryVisibility { + /** Column ID to control summary visibility for */ + columnId: string; + /** Show or hide summary for this column */ + visible: boolean; +} +/** + * Display summary configuration + * @group Visual Overrides + */ +export interface DisplaySummaryConfig { + /** Show all column summaries by default */ + showAllSummaries?: boolean; + /** Per-column summary visibility overrides */ + columnVisibility?: ColumnSummaryVisibility[]; +} +/** + * Table visualization overrides + * @group Visual Overrides + */ +export interface TableOverrides { + /** Per-column configurations (properties, conditional formatting) */ + columns?: TableColumn[]; + /** Table display properties (theme, density) */ + display?: TableDisplay; + /** Summary/headline column visibility configuration */ + displaySummaryConfig?: DisplaySummaryConfig; + /** Update mask paths for partial updates */ + updateMaskPaths?: string[]; +} +/** + * Visualization overrides to customize chart and table rendering + * within embedded ThoughtSpot components. + * + * @group Visual Overrides + * @example + * ```js + * const embed = new AppEmbed('#tsEmbed', { + * visualOverrides: { + * chart: { + * legend: { show: true, position: 'bottom' }, + * columns: [{ name: 'Revenue', color: '#1f77b4' }], + * }, + * table: { + * display: { tableTheme: 'ZEBRA', tableContentDensity: 'COMPACT' }, + * }, + * }, + * }); + * ``` + */ +export interface VisualizationOverrides { + /** Chart visualization overrides */ + chart?: ChartOverrides; + /** Table visualization overrides */ + table?: TableOverrides; +} +//# sourceMappingURL=types.d.ts.map \ No newline at end of file diff --git a/dist/src/types.d.ts.map b/dist/src/types.d.ts.map new file mode 100644 index 00000000..2ba47a03 --- /dev/null +++ b/dist/src/types.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6CAA6C,CAAC;AAEpF;;;;GAIG;AAEH,oBAAY,QAAQ;IAChB;;;;;;;;;;OAUG;IACH,IAAI,SAAS;IACb;;;;;;;;;;;;;;;;OAgBG;IACH,WAAW,gBAAgB;IAC3B;;;;OAIG;IACH,GAAG,aAAa;IAChB;;;;OAIG;IAEH,IAAI,aAAa;IACjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2CG;IAEH,YAAY,aAAa;IACzB;;;;;OAKG;IACH,IAAI,aAAa;IACjB;;;;OAIG;IAEH,YAAY,aAAa;IACzB;;;;;OAKG;IACH,UAAU,eAAe;IACzB;;;;;;;;;;;;;;;;OAgBG;IAEH,gBAAgB,eAAe;IAC/B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,0BAA0B,yBAAyB;IACnD;;;;;OAKG;IACH,KAAK,UAAU;CAClB;AACD;;;;GAIG;AAEH,oBAAY,eAAe;IACvB;;;;OAIG;IACH,UAAU,gBAAgB;IAC1B;;;;OAIG;IACH,IAAI,kBAAkB;IACtB;;;;OAIG;IACH,UAAU,eAAe;IACzB;;;;OAIG;IACH,OAAO,YAAY;IACnB;;;;OAIG;IACH,mBAAmB,mBAAmB;IACtC;;;;OAIG;IACH,cAAc,oBAAoB;IAClC;;;;OAIG;IACH,kBAAkB,wBAAwB;IAC1C;;;;;OAKG;IACH,MAAM,WAAW;IACjB;;;;;OAKG;IACH,OAAO,YAAY;IACnB;;;;;OAKG;IACH,SAAS,cAAc;CAC1B;AACD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,WAAW,CAAC;AAE/C;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IAC/B;;;;;OAKG;IACH,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IAEH,cAAc,CAAC,EAAE;QACb,CAAC,QAAQ,EAAE,MAAM,GAAG;YAChB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;SACjC,CAAC;KACL,CAAC;CACL;AACD;;GAEG;AACH,MAAM,WAAW,YAAY;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,kBAAkB,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,WAAW,uBAAuB;IACpC,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,OAAO,CAAC,EAAE;QACN;;WAEG;QACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC9B,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACnC,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACtB,CAAC;IACF,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IACxB;;OAEG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB;;OAEG;IACH,QAAQ,EAAE,QAAQ,CAAC;IACnB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;OAUG;IACH,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACrC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,gBAAgB;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;;;OAKG;IACH,0BAA0B,CAAC,EAAE,OAAO,CAAC;IAErC;;;;;;;OAOG;IACH,2BAA2B,CAAC,EAAE,OAAO,CAAC;IAEtC;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAE/B;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;;;;;OAOG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAE/B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAE5B;;;;;;;;OAQG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC;;;OAGG;IACH,8BAA8B,CAAC,EAAE,OAAO,CAAC;IACzC;;;OAGG;IACH,cAAc,CAAC,EAAE,uBAAuB,CAAC;IACzC;;;;;;;;;;;;;OAaG;IACH,oBAAoB,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IAC5C;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,0BAA0B,CAAC,EAAE,OAAO,CAAC;IAErC;;;OAGG;IACH,UAAU,CAAC,EAAE;QACT,YAAY,EAAE,MAAM,CAAC;QACrB,aAAa,EAAE,MAAM,CAAC;QACtB,eAAe,EAAE,MAAM,CAAC;KAC3B,CAAC;IAEF;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;;;;;;;;;OAYG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAEnC;;;OAGG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;;;;;;;;;;;;;;;;;OAkBG;IACH,eAAe,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAC/D;;;;;;;;;;;;;;OAcG;IACH,iCAAiC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAExD,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;;;;;;;;;;OAYG;IACH,6BAA6B,CAAC,EAAE,OAAO,CAAC;IAExC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+JG;IACH,aAAa,CAAC,EAAE,YAAY,EAAE,CAAC;IAE/B;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B;AAGD,MAAM,WAAW,YAAY;CAAG;AAEhC;;;GAGG;AACH,MAAM,WAAW,WAAW;IACxB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IACpC;;;OAGG;IACH,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;CACxD;AAED;;GAEG;AACH,MAAM,WAAW,cAAe,SAAQ,iBAAiB;IACrD;;OAEG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B;;;;;;;;;;;;;;;;;OAiBG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IAEH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;;;;;;;;;;;OAiBG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B;;;;;;;;;;;;;;OAcG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;;;;;;;;;;;OAiBG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB;;;;;;;;;;;;;;;;;;OAkBG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,eAAe,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAC/D;;;;;OAKG;IACH,cAAc,CAAC,EAAE,uBAAuB,CAAC;IACzC;;;;;;;;;;;;;;OAcG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;;OAOG;IACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC;;;;;;;;;;;;;;;;OAgBG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;;;;;;;;;;;;;;;OAkBG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;;;;;;;;;;;;;OAcG;IAEH,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;;;;;;;;;;;OAcG;IACH,+BAA+B,CAAC,EAAE,OAAO,CAAC;IAC1C;;;;;;;;;;;;;;;;;OAiBG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;;;;;;;;OAkBG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;;;;;;;;;;;;;OAcG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;;;;;;;OAYG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiLG;IACH,aAAa,CAAC,EAAE,YAAY,EAAE,CAAC;IAE/B;;;;;;;;;;;OAWG;IACH,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC;;;;;;;;;;;OAWG;IACH,6BAA6B,CAAC,EAAE,OAAO,CAAC;IAExC;;;;;;;;;;;OAWG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,8BAA8B,GAAG,IAAI,CAC7C,cAAc,EACZ,aAAa,GACb,2BAA2B,GAC3B,yBAAyB,GACzB,iBAAiB,GACjB,eAAe,GACf,4BAA4B,CACjC,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,cAAc;IAC3B;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,iBAAiB,CAAC,EAAE,eAAe,EAAE,CAAC;IACtC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,qBAAqB,CAAC,EAAE,cAAc,EAAE,CAAC;IACzC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,wBAAwB,CAAC,EAAE,cAAc,EAAE,CAAC;IAC5C;;;;;;;;;;;;;;;;;;;OAmBG;IACH,sBAAsB,CAAC,EAAE,eAAe,EAAE,CAAC;CAC9C;AAED;;GAEG;AACH,MAAM,WAAW,+BAA+B;IAC5C;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;IACjC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACvC;;;;;;;;;;;;;OAaG;IACH,kBAAkB,CAAC,EAAE,yBAAyB,CAAC;IAC/C;;;;;;;;;;;OAWG;IACH,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC;;;;;;;;;;;OAWG;IACH,+BAA+B,CAAC,EAAE,OAAO,CAAC;IAC1C;;;;;;;;;;;;;;OAcG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;;;;;OAcG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;;;;;;;;;;OAcG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC;;;;;;;;;;;;;;OAcG;IACH,gCAAgC,CAAC,EAAE,OAAO,CAAC;CAC9C;AAED;;GAEG;AACH,MAAM,WAAW,2BAA2B;IACxC;;;;;;;;;;;;;;OAcG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;;;;;;;;;;;OAcG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;;;;;;;;;;OAcG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC;;;;;;;;;;;;;OAaG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;;;;;;;;;;;;;;OAeG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;;;;;;;;;;;;OAeG;IACH,+BAA+B,CAAC,EAAE,OAAO,CAAC;IAC1C;;;;;;;;;;;;;;;OAeG;IACH,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,kCAAkC,CAAC,EAAE,OAAO,CAAC;IAC7C;;;;;;;;;;;;;;;OAeG;IACH,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC;;;;;;;;;;;;;;OAcG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;;;;;;;;OAcG;IACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC;;;;;;;;;;;;;;;;OAgBG;IACH,qCAAqC,CAAC,EAAE,OAAO,CAAC;IAChD;;;;;;;;;;;;;OAaG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAElC;;;;;;;;;;;;;;OAcG;IACH,oCAAoC,CAAC,EAAE,OAAO,CAAC;IAC/C;;;;;;;;;;;;;;OAcG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;;;;;;;;;;;OAcG;IACH,8BAA8B,CAAC,EAAE,OAAO,CAAC;IACzC;;;;;;;;;;;;;;OAcG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,WAAW,kBACb,SAAQ,cAAc,EAClB,+BAA+B,EAC/B,cAAc,EACd,2BAA2B;CAAG;AAEtC;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG;IAEzB,IAAI,EAAE,MAAM,CAAC;IAEb,IAAI,EAAE,GAAG,CAAC;IAEV,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AACF;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG;IACzB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AACF;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,CAE1B,OAAO,EAAE,cAAc;AACvB;;;GAGG;AACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,KAC9B,IAAI,CAAC;AACV;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC7B;;;OAGG;IAEH,OAAO,EAAE,cAAc,CAAC;IACxB,QAAQ,EAAE,eAAe,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC;AAExD,MAAM,MAAM,WAAW,GAAG;IACtB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;CAC5C,CAAC;AAEF;;GAEG;AAEH,oBAAY,eAAe;IACvB;;OAEG;IACH,EAAE,OAAO;IACT;;OAEG;IACH,EAAE,OAAO;IACT;;OAEG;IACH,EAAE,OAAO;IACT;;OAEG;IACH,EAAE,OAAO;IACT;;OAEG;IACH,EAAE,OAAO;IACT;;OAEG;IACH,EAAE,OAAO;IACT;;OAEG;IACH,QAAQ,aAAa;IACrB;;OAEG;IACH,WAAW,gBAAgB;IAC3B;;OAEG;IACH,SAAS,cAAc;IACvB;;OAEG;IACH,UAAU,eAAe;IACzB;;OAEG;IACH,UAAU,eAAe;IACzB;;OAEG;IACH,MAAM,WAAW;IACjB;;OAEG;IACH,EAAE,OAAO;IACT;;OAEG;IACH,EAAE,OAAO;IACT;;OAEG;IACH,MAAM,WAAW;CACpB;AAED;;;;;;;GAOG;AAEH,oBAAY,cAAc;IACtB;;OAEG;IACH,MAAM,WAAW;IACjB;;OAEG;IACH,SAAS,cAAc;IACvB;;OAEG;IACH,QAAQ,aAAa;IACrB;;OAEG;IACH,SAAS,eAAe;IACxB;;OAEG;IACH,QAAQ,aAAa;IACrB;;OAEG;IACH,QAAQ,aAAa;CACxB;AAED;;;;GAIG;AAEH,oBAAY,eAAe;IACvB;;OAEG;IACH,SAAS,cAAc;IACvB;;;OAGG;IACH,SAAS,cAAY;IACrB;;OAEG;IACH,IAAI,SAAS;IACb;;OAEG;IACH,MAAM,WAAW;IACjB;;OAEG;IACH,QAAQ,cAAc;IACtB;;OAEG;IACH,KAAK,UAAU;IACf;;OAEG;IACH,QAAQ,aAAa;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC1B;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,QAAQ,EAAE,eAAe,CAAC;IAC1B;;;;OAIG;IACH,MAAM,EAAE,CAAC,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;CAClD;AACD;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC7B;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;CACpC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,oBAAY,UAAU;IAClB;;;;;;;;;;;OAWG;IACH,IAAI,SAAS;IACb;;;;;;;;;;OAUG;IACH,QAAQ,aAAa;IACrB;;;;;;;;;;;;OAYG;IACH,IAAI,SAAS;IACb;;;;;;;;;;;;OAYG;IACH,IAAI,SAAS;IACb;;;;;;;OAOG;IACH,YAAY,iBAAiB;IAC7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmCG;IACH,SAAS,cAAc;IACvB;;;;;;;;;;OAUG;IACH,kBAAkB,uBAAuB;IACzC;;;;;;;;;;OAUG;IACH,gBAAgB,qBAAqB;IACrC;;;;;;;;;;;;;;;OAeG;IACH,YAAY,iBAAiB;IAC7B;;;;;;;;;;OAUG;IACH,mBAAmB,wBAAwB;IAC3C;;;;;;;;;;;;;;;;;OAiBG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAkDG;IACH,KAAK,UAAU;IACf;;;;;;;;OAQG;IACH,KAAK,UAAU;IACf;;;;;;;;;;;OAWG;IACH,UAAU,2BAA2B;IACrC;;;OAGG;IACH,WAAW,2BAA2B;IAEtC;;;OAGG;IACH,kBAAkB,uBAAuB;IAEzC;;;OAGG;IACH,UAAU,0BAA0B;IACpC;;;;OAIG;IACH,WAAW,iBAAiB;IAC5B;;;;OAIG;IACH,iBAAiB,sBAAsB;IACvC;;;;;;;;;;;OAWG;IACH,YAAY,iBAAiB;IAC7B;;;;;;;;OAQG;IACH,WAAW,iBAAiB;IAC5B;;;OAGG;IACH,MAAM,0BAA0B;IAChC;;;;;;;;;OASG;IACH,cAAc,mBAAmB;IACjC;;;;OAIG;IACH,YAAY,iBAAiB;IAC7B;;;;;;;;;OASG;IACH,UAAU,gBAAgB;IAC1B;;;;;;;;;OASG;IACH,WAAW,iBAAiB;IAC5B;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,iBAAiB,qBAAqB;IACtC;;;;;;;;;OASG;IACH,GAAG,MAAM;IACT;;;;;;;;;;;;;;;;;OAiBG;IACH,IAAI,SAAS;IACb;;;;;;;;;;;;;;OAcG;IACH,QAAQ,aAAa;IACrB;;;;;;;;;;;;;;OAcG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;;;;;;;;OAcG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;;;;;;;;OAcG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;;;;;;;;OAcG;IACH,cAAc,mBAAmB;IACjC;;;;;;;;;OASG;IACH,gCAAgC,qCAAqC;IACrE;;;;;;;;;;;;;;OAcG;IACH,YAAY,iBAAiB;IAC7B;;;;;;;;;OASG;IACH,YAAY,iBAAiB;IAC7B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,GAAG,QAAQ;IACX;;;;;;;;;;;;;;;;OAgBG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;;;;;;;;;;OAgBG;IACH,KAAK,UAAU;IACf;;;;;;;;;;OAUG;IACH,YAAY,8BAA8B;IAC1C;;;;;;;;;;OAUG;IACH,YAAY,8BAA8B;IAC1C;;;;;;;;;OASG;IACH,eAAe,wCAAwC;IACvD;;;;;;;;;OASG;IACH,SAAS,cAAc;IACvB;;;;;;;;;;OAUG;IACH,OAAO,YAAY;IACnB;;;;;;;;;;;;;;;OAeG;IACH,SAAS,cAAc;IACvB;;;;;;;;;OASG;IACH,UAAU,eAAe;IACzB;;;;;;;;;;;;;;OAcG;IACH,SAAS,cAAc;IACvB;;;;;;;;;OASG;IACH,kBAAkB,uBAAuB;IACzC;;;;;;;;;OASG;IACH,mBAAmB,wBAAwB;IAC3C;;;OAGG;IACH,QAAQ,YAAY;IACpB;;;OAGG;IACH,gBAAgB,mBAAmB;IACnC;;;;;;;;;OASG;IACH,aAAa,iBAAiB;IAC9B;;;;;;;;;OASG;IACH,cAAc,mBAAmB;IACjC;;;;;;;;;OASG;IACH,QAAQ,iBAAiB;IACzB;;;;;;;;;OASG;IACH,IAAI,SAAS;IACb;;;;;;;;;OASG;IACH,SAAS,cAAc;IACvB;;;;;;;;;;;;;OAaG;IACH,OAAO,YAAY;IACnB;;;;;;;;OAQG;IACH,MAAM,WAAW;IACjB;;;;;;;OAOG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;OAOG;IACH,MAAM,WAAW;IACjB;;;;;;;;OAQG;IACH,OAAO,YAAY;IACnB;;;;;;;;OAQG;IACH,QAAQ,kBAAkB;IAC1B;;;;;;;;;OASG;IACH,kBAAkB,yBAAyB;IAC3C;;;;;;;;;OASG;IACH,kBAAkB,uBAAuB;IACzC;;;OAGG;IACH,eAAe,oBAAoB;IACnC;;;;;;;;;;;;OAYG;IACH,aAAa,kBAAkB;IAC/B;;;OAGG;IACH,gBAAgB,qBAAqB;IACrC;;;OAGG;IACH,gBAAgB,qBAAqB;IACrC;;;;;;;;;;OAUG;IACH,sBAAsB,2BAA2B;IACjD;;;;;;;;OAQG;IACH,sBAAsB,2BAAyB;IAC/C;;;;;;;;;OASG;IACH,oBAAoB,yBAAyB;IAC7C;;;;;;;OAOG;IACH,oBAAoB,yBAAuB;IAC3C;;;;;;;OAOG;IACH,cAAc,mBAAmB;IACjC;;;;;;;OAOG;IACH,sBAAsB,2BAA2B;IACjD;;;;;OAKG;IACH,sBAAsB,2BAAyB;IAC/C;;;;;;;;;;;;;;;;;;;OAmBG;IACH,sBAAsB,2BAA2B;IACjD;;;OAGG;IACH,eAAe,oBAAoB;IACnC;;;;;;;OAOG;IACH,WAAW,gBAAgB;IAC3B;;;OAGG;IACH,MAAM,WAAW;IACjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgEG;IACH,2BAA2B,gCAAgC;IAC3D;;;;;;;;;OASG;IACH,gBAAgB,qBAAqB;IACrC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAyCG;IACH,gBAAgB,qBAAqB;IACrC;;;;;;;;;;;OAWG;IACH,eAAe,oBAAoB;IACnC;;;OAGG;IACH,WAAW,gBAAgB;IAC3B;;;;OAIG;IACH,eAAe,oBAAoB;IACnC;;;;OAIG;IACH,8BAA8B,mCAAmC;IACjE;;;;;;;;;OASG;IACH,WAAW,gBAAgB;IAC3B;;;;;;;;;OASG;IACH,kBAAkB,uBAAuB;IACzC;;;;;;;;;OASG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;;;OASG;IACH,qBAAqB,0BAA0B;IAC/C;;;;;;;;;OASG;IACH,qBAAqB,0BAA0B;IAC/C;;;;;;;;;OASG;IACH,gBAAgB,qBAAqB;IACrC;;;;;;;;;OASG;IACH,iBAAiB,sBAAsB;IACvC;;;;;;;;;OASG;IACH,wBAAwB,6BAA6B;IACrD;;;;;;;;;OASG;IACH,WAAW,gBAAgB;IAC3B;;;;;;;;;OASG;IACH,mBAAmB,wBAAwB;IAC3C;;;;;;;;;;OAUG;IACH,kBAAkB,uBAAuB;IACzC;;;;;;;;;OASG;IACH,WAAW,gBAAgB;IAC3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgEG;IACH,YAAY,iBAAiB;IAC7B;;;;;;;;;;OAUG;IACH,0BAA0B,+BAA+B;IACzD;;;;;;;;;;OAUG;IACH,0BAA0B,+BAA+B;IACzD;;;;;;;;;;OAUG;IACH,2BAA2B,gCAAgC;IAE3D;;;;;;;;;;OAUG;IACH,gBAAgB,qBAAqB;IAErC;;;;;;;;;OASG;IACH,uBAAuB,4BAA4B;IAEnD;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,UAAU,eAAe;IAEzB;;;;;;;;;;;;OAYG;IACH,qBAAqB,0BAA0B;IAE/C;;;;;;;;;;OAUG;IACH,cAAc,mBAAmB;IAEjC;;;;;;;;;;OAUG;IACH,wBAAwB,6BAA6B;IAErD;;;;;;;;;;OAUG;IACH,0BAA0B,+BAA+B;IAEzD;;;;;;;;;;OAUG;IACH,2BAA2B,gCAAgC;IAE3D;;;;;;;;;;OAUG;IACH,4BAA4B,iCAAiC;IAE7D;;;;;;;;;OASG;IACH,eAAe,oBAAoB;IAEnC;;;;;;;;;OASG;IACH,gBAAgB,qBAAqB;IACrC;;;;;;;;;;;;OAYG;IACH,4BAA4B,iCAAiC;CAChE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuEG;AAEH,oBAAY,SAAS;IACjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,MAAM,WAAW;IACjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmEG;IACH,SAAS,qBAAqB;IAC9B;;;OAGG;IACH,MAAM,WAAW;IACjB;;;OAGG;IACH,MAAM,WAAW;IACjB;;;;;;;;;;;;;;;OAeG;IACH,YAAY,iBAAiB;IAC7B;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,cAAc,2BAA2B;IACzC;;;;;;;;;;;;;;;;;;OAkBG;IACH,YAAY,iBAAiB;IAC7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAyCG;IACH,oBAAoB,yBAAyB;IAC7C;;;;;;;;;;OAUG;IACH,QAAQ,aAAa;IACrB;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,UAAU,eAAe;IACzB;;;;;;;;;;;;;;;;OAgBG;IACH,UAAU,eAAe;IACzB;;;;;;;;;;;;;;;;OAgBG;IACH,YAAY,iBAAiB;IAC7B;;;;;;;;;;;;;;OAcG;IACH,kCAAkC,uCAAuC;IACzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAoGG;IACH,GAAG,QAAQ;IACX;;;;;;;;;;;;;;OAcG;IACH,aAAa,iBAAiB;IAC9B;;;;;;;;;;;;;OAaG;IACH,QAAQ,iBAAiB;IACzB;;;;;;;;;;;;;OAaG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,SAAS,cAAc;IACvB;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,OAAO,YAAY;IACnB;;;;;;;;;;;;;OAaG;IACH,SAAS,cAAc;IACvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAqCG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;;;;OAUG;IACH,gCAAgC,qCAAqC;IACrE;;;;;;;;OAQG;IACH,YAAY,iBAAiB;IAC7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2CG;IACH,SAAS,cAAc;IACvB;;;;;;;;;;;;OAYG;IACH,MAAM,WAAW;IACjB;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,YAAY;IACnB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiCG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2CG;IACH,IAAI,SAAS;IACb;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2CG;IACH,QAAQ,kBAAkB;IAC1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2CG;IACH,OAAO,YAAY;IACnB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4CG;IACH,MAAM,WAAW;IACjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACH,kBAAkB,uBAAuB;IACzC;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,MAAM,mBAAmB;IACzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,QAAQ,kBAAkB;IAC1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IAEH,aAAa,kBAAkB;IAC/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAqCG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAqCG;IACH,cAAc,mBAAmB;IACjC;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,KAAK,UAAU;IACf;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAkDG;IACH,IAAI,SAAS;IACb;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,YAAY,mBAAmB;IAC/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,eAAe,uBAAuB;IACtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,eAAe,oBAAoB;IACnC;;;;;;;;;;;;;;;;OAgBG;IACH,WAAW,gBAAgB;IAC3B;;;;;;;;;;;;;;;;OAgBG;IACH,UAAU,eAAe;IACzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAqGG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,YAAY;IACnB;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,cAAc,2BAA2B;IACzC;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,aAAa,0BAA0B;IACvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,gBAAgB,qBAAqB;IACrC;;;;;;;;OAQG;IACH,OAAO,YAAY;IACnB;;;;;;;;;;;;;;OAcG;IACH,iBAAiB,sBAAsB;IACvC;;;;;;;;;OASG;IACH,8BAA8B,mCAAmC;IACjE;;;;;;;OAOG;IACH,8BAA8B,mCAAiC;IAC/D;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,gBAAgB,qBAAqB;IACrC;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;;;;;;;;;;OAgBG;IACH,sBAAsB,2BAA2B;IACjD;;;;;;OAMG;IACH,sBAAsB,2BAAyB;IAC/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACH,sBAAsB,2BAA2B;IACjD;;;;;;;OAOG;IACH,WAAW,gBAAgB;IAC3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2DG;IACH,UAAU,eAAe;IACzB;;;OAGG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;;;;;;;;;;;;OAkBG;IACH,qBAAqB,0BAA0B;IAC/C;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;;;;;;;;OAcG;IACH,cAAc,mBAAmB;IACjC;;;;;;;;;;;;;OAaG;IACH,kBAAkB,uBAAuB;IACzC;;;;;;;;;OASG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;OAOG;IACH,qBAAqB,0BAA0B;IAC/C;;;;;;;OAOG;IACH,wBAAwB,6BAA6B;IACrD;;;;;;;OAOG;IACH,gBAAgB,qBAAqB;IACrC;;;;;;;;;;;;;OAaG;IACH,mBAAmB,wBAAwB;IAC3C;;;;;;;;OAQG;IACH,eAAe,oBAAoB;IACnC;;;;;;;;;;;OAWG;IACH,uBAAuB,4BAA4B;IACnD;;;;;;;;;;;;;OAaG;IACH,UAAU,eAAe;IAEzB;;;;;;;;OAQG;IACH,iBAAiB,sBAAsB;IACvC;;;;;;;OAOG;IACH,YAAY,mBAAmB;IAC/B;;;;;;;;;;;;OAYG;IACH,2BAA2B,gCAAgC;IAE3D;;;;;;;;;OASG;IACH,cAAc,mBAAmB;IACjC;;;;;;;;;;;;;;;;;;OAkBG;IACH,qBAAqB,0BAA0B;IAE/C;;;;;;;;;;OAUG;IACH,yBAAyB,8BAA8B;IAEvD;;;;;;;OAOG;IACH,0BAA0B,+BAA+B;IACzD;;;;;;;;OAQG;IACH,4BAA4B,iCAAiC;CAChE;AAED;;;GAGG;AAEH,oBAAY,oBAAoB;IAC5B;;OAEG;IACH,MAAM,SAAS;IACf;;OAEG;IACH,SAAS,aAAa;IACtB;;OAEG;IACH,QAAQ,WAAW;CACtB;AAED;;;GAGG;AAEH,oBAAY,KAAK;IACb,KAAK,UAAU;IACf,QAAQ,aAAa;IACrB,WAAW,gBAAgB;IAC3B,cAAc,mBAAmB;IACjC,cAAc,kBAAkB;IAChC,mBAAmB,gBAAgB;IACnC,UAAU,eAAe;IACzB,6BAA6B,iCAAiC;IAC9D,WAAW,gBAAgB;IAC3B,WAAW,eAAe;IAC1B,WAAW,gBAAgB;IAC3B,UAAU,eAAe;IACzB,wBAAwB,uBAAuB;IAC/C,kBAAkB,uBAAuB;IACzC,6BAA6B,6BAA6B;IAC1D,eAAe,oBAAoB;IACnC,UAAU,eAAe;IACzB,yBAAyB,2BAA2B;IACpD,GAAG,QAAQ;IACX,kBAAkB,uBAAuB;IACzC,SAAS,cAAc;IACvB,iBAAiB,sBAAsB;IACvC,aAAa,kBAAkB;IAC/B,UAAU,yBAAyB;IACnC,eAAe,qBAAqB;IACpC,WAAW,kBAAkB;IAC7B,QAAQ,eAAe;IACvB,YAAY,yBAAyB;IACrC,OAAO,eAAe;IACtB,cAAc,mBAAmB;IACjC,aAAa,kBAAkB;IAC/B,cAAc,kBAAkB;IAChC,oBAAoB,yBAAyB;IAC7C,WAAW,wBAAwB;IACnC,kBAAkB,wBAAwB;IAC1C,kBAAkB,sBAAsB;IACxC,UAAU,eAAe;IACzB,MAAM,WAAW;IACjB,WAAW,gBAAgB;IAC3B,qBAAqB,0BAA0B;IAE/C,QAAQ,aAAa;IACrB,aAAa,eAAe;IAC5B,UAAU,eAAe;IAMzB,kBAAkB,mCAAmC;IACrD,YAAY,iBAAiB;IAC7B,qBAAqB,0BAA0B;IAC/C,0BAA0B,+BAA+B;IACzD,iBAAiB,oBAAoB;IACrC,gBAAgB,qBAAqB;IACrC,iBAAiB,iCAAiC;IAClD,iBAAiB,sBAAsB;IACvC,aAAa,kBAAkB;IAC/B,gBAAgB,qBAAqB;IACrC,gBAAgB,qBAAqB;IACrC,uBAAuB,8BAA8B;IACrD,eAAe,sBAAsB;IACrC,qBAAqB,0BAA0B;IAC/C,sBAAsB,2BAA2B;IACjD,mBAAmB,wBAAwB;IAC3C,sBAAsB,2BAA2B;IACjD,iBAAiB,sBAAsB;IACvC,qBAAqB,0BAA0B;IAC/C,2BAA2B,gCAAgC;IAC3D,mBAAmB,wBAAwB;IAC3C,wBAAwB,6BAA6B;IACrD,kBAAkB,uBAAuB;IACzC,oBAAoB,yBAAyB;IAC7C,8BAA8B,mCAAmC;IACjE,qBAAqB,6BAA6B;IAClD,UAAU,aAAa;IACvB,WAAW,gBAAgB;IAC3B,YAAY,iBAAiB;IAC7B,mBAAmB,wBAAwB;IAC3C,WAAW,cAAc;IACzB,KAAK,UAAU;IACf,mBAAmB,wBAAwB;IAC3C,4BAA4B,0BAA0B;IACtD,eAAe,oBAAoB;IACnC,eAAe,oBAAoB;IACnC,gBAAgB,uBAAuB;IACvC,qBAAqB,4BAA4B;IACjD,aAAa,kBAAkB;IAC/B,kBAAkB,uBAAuB;IACzC,oBAAoB,yBAAyB;IAC7C,aAAa,kBAAkB;IAC/B,cAAc,mBAAmB;IACjC,oBAAoB,yBAAyB;IAC7C,qBAAqB,wBAAwB;IAC7C,aAAa,kBAAkB;IAC/B,0BAA0B,+BAA+B;IACzD,0CAA0C,+CAA+C;IACzF,wBAAwB,6BAA6B;IACrD,gBAAgB,qBAAqB;IACrC,kBAAkB,uBAAuB;IACzC,cAAc,mBAAmB;IACjC,mBAAmB,wBAAwB;IAC3C,cAAc,mBAAmB;IACjC,oCAAoC,yCAAyC;IAC7E,sBAAsB,2BAA2B;IACjD,+BAA+B,oCAAoC;IACnE,qBAAqB,0BAA0B;IAC/C,0BAA0B,+BAA+B;IACzD,2BAA2B,gCAAgC;IAC3D,iBAAiB,+BAA+B;IAChD,0BAA0B,oCAAoC;IAC9D,oCAAoC,mCAAmC;IACvE,cAAc,+BAA+B;IAC7C,gCAAgC,qCAAqC;IACrE,aAAa,UAAU;IACvB,oBAAoB,yBAAyB;IAC7C,eAAe,oBAAoB;IACnC,YAAY,iBAAiB;IAC7B,YAAY,iBAAiB;IAC7B,sBAAsB,2BAA2B;IACjD,yBAAyB,2CAA2C;IACpE,aAAa,kBAAkB;IAC/B,mBAAmB,wBAAwB;IAC3C,oCAAoC,yCAAyC;IAC7E,4BAA4B,iCAAiC;IAC7D,qBAAqB,0BAA0B;IAC/C,6BAA6B,kCAAkC;IAC/D,4BAA4B,iCAAiC;IAC7D,iCAAiC,sCAAsC;IACvE,iCAAiC,sCAAsC;IACvE,mCAAmC,wCAAwC;IAC3E,qCAAqC,0CAA0C;IAC/E,uBAAuB,4BAA4B;IACnD,8BAA8B,mCAAmC;IACjE,wBAAwB,6BAA6B;IACrD,+BAA+B,oCAAoC;IACnE,mBAAmB,wBAAwB;IAC3C,6BAA6B,kCAAkC;IAC/D,sBAAsB,2BAA2B;IACjD,sBAAsB,2BAA2B;IACjD,mCAAmC,wCAAwC;IAC3E,mCAAmC,wCAAwC;IAC3E,uBAAuB,4BAA4B;IACnD,yBAAyB,8BAA8B;IACvD,6BAA6B,kCAAkC;IAC/D,yBAAyB,8BAA8B;IACvD,gCAAgC,qCAAqC;IACrE,4BAA4B,iCAAiC;IAC7D,6BAA6B,kCAAkC;IAC/D,0BAA0B,+BAA+B;IACzD,wBAAwB,6BAA6B;IACrD,wBAAwB,6BAA6B;IACrD,0BAA0B,+BAA+B;CAC5D;AAED;;;GAGG;AACH,MAAM,MAAM,0BAA0B,GAAG;IACrC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,oBAAY,MAAM;IACd;;;;;;;OAOG;IACH,IAAI,SAAS;IACb;;OAEG;IACH,MAAM,WAAW;IACjB;;OAEG;IACH,YAAY,iBAAiB;IAC7B;;;;;;;;OAQG;IACH,UAAU,eAAe;IACzB;;;;;;;;;;;OAWG;IACH,SAAS,cAAc;IACvB;;;;;;;OAOG;IACH,SAAS,cAAc;IACvB;;;;;;;OAOG;IACH,QAAQ,kBAAkB;IAC1B;;OAEG;IACH,WAAW,gBAAgB;IAC3B;;;;;;;;OAQG;IACH,QAAQ,iBAAiB;IACzB;;;;;;;OAOG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;OAOG;IACH,KAAK,UAAU;IACf;;;;;;;OAOG;IACH,SAAS,cAAc;IACvB;;;;;;;;;OASG;IACH,mBAAmB,wBAAwB;IAC3C;;;;;;;;;OASG;IACH,eAAe,oBAAoB;IACnC;;;;;;;;;OASG;IACH,mBAAmB,wBAAwB;IAC3C;;;;;;;;;;OAUG;IACH,iBAAiB,sBAAsB;IACvC;;;;;;;OAOG;IACH,iBAAiB,sBAAsB;IACvC;;;;;;;OAOG;IACH,UAAU,eAAe;IACzB;;;;;;;OAOG;IACH,YAAY,iBAAiB;IAC7B;;;;;;;;OAQG;IACH,YAAY,oBAAoB;IAChC;;;;;;;;OAQG;IACH,WAAW,sBAAsB;IACjC;;OAEG;IACH,WAAW,gBAAgB;IAC3B;;;;;;;OAOG;IACH,aAAa,kBAAkB;IAC/B;;OAEG;IACH,cAAc,mBAAmB;IACjC;;OAEG;IACH,YAAY,iBAAiB;IAC7B;;OAEG;IACH,QAAQ,aAAa;IACrB;;OAEG;IACH,YAAY,iBAAiB;IAC7B;;;;;;;;;OASG;IACH,kBAAkB,uBAAuB;IACzC;;;;;;;;OAQG;IACH,QAAQ,aAAa;IACrB;;;;;;;;OAQG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;;;;;OAWG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;;OAQG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;;OAQG;IACH,cAAc,mBAAmB;IACjC;;;;;;;;OAQG;IACH,iBAAiB,sBAAsB;IACvC;;;;;;;;OAQG;IACH,gCAAgC,qCAAqC;IACrE;;;;;;;;;;;;OAYG;IACH,wBAAwB,6BAA6B;IACrD;;;;;;;;OAQG;IACH,uBAAuB,4BAA4B;IACnD;;;;;;;;OAQG;IACH,sBAAsB,2BAA2B;IACjD;;OAEG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;;;;OAUG;IACH,SAAS,cAAc;IACvB;;;;;;;;OAQG;IACH,SAAS,cAAc;IACvB;;;;;;;OAOG;IACH,SAAS,cAAc;IACvB;;;;;;;OAOG;IACH,OAAO,YAAY;IACnB;;;;;;;;OAQG;IACH,OAAO,YAAY;IACnB;;;;;;;;;;OAUG;IACH,UAAU,eAAe;IACzB;;;;;;;;OAQG;IACH,IAAI,SAAS;IACb;;;;;;OAMG;IACH,SAAS,cAAc;IACvB;;;;;;;;OAQG;IACH,MAAM,WAAW;IACjB;;OAEG;IACH,OAAO,YAAY;IACnB;;OAEG;IACH,QAAQ,aAAa;IACrB;;OAEG;IACH,MAAM,WAAW;IACjB;;OAEG;IACH,kBAAkB,uBAAuB;IACzC;;OAEG;IACH,YAAY,iBAAiB;IAC7B;;;;;;;;;OASG;IAEH,aAAa,iBAAiB;IAC9B;;OAEG;IACH,kBAAkB,iBAAiB;IACnC;;OAEG;IACH,sBAAsB,2BAA2B;IACjD;;;;;;;OAOG;IACH,GAAG,QAAQ;IACX;;OAEG;IACH,YAAY,iBAAiB;IAC7B;;;;;;;OAOG;IAEH,YAAY,iBAAiB;IAC7B;;;;;;OAMG;IACH,OAAO,YAAY;IACnB;;;;;;;;OAQG;IAEH,YAAY,8BAA8B;IAC1C;;;;;;;OAOG;IACH,YAAY,8BAA8B;IAC1C;;;;;;;;OAQG;IACH,eAAe,wCAAwC;IACvD,WAAW,oCAAoC;IAC/C;;OAEG;IACH,SAAS,2BAA2B;IACpC,WAAW,mCAAmC;IAC9C,SAAS,gCAAgC;IACzC;;;;;;;;OAQG;IACH,SAAS,UAAU;IACnB;;;;;;;OAOG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;;;;;;;;OAcG;IACH,mBAAmB,wBAAwB;IAC3C;;;;;;;;OAQG;IACH,YAAY,mBAAmB;IAC/B;;;;;;;;;;OAUG;IACH,mBAAmB,wBAAwB;IAC3C;;;;;;;;;;OAUG;IACH,cAAc,mBAAmB;IACjC;;;;;;;OAOG;IACH,WAAW,gBAAgB;IAC3B;;;;;;;;;OASG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;OAOG;IACH,WAAW,gBAAgB;IAC3B;;;;;;;;OAQG;IACH,YAAY,mBAAmB;IAC/B;;;;;;;;;OASG;IACH,eAAe,uBAAuB;IACtC;;;;;;;;OAQG;IACH,eAAe,oBAAoB;IACnC;;;;;;;;OAQG;IACH,WAAW,mCAAmC;IAC9C;;;;;;;;OAQG;IACH,WAAW,gBAAgB;IAC3B;;;;;;;;OAQG;IACH,WAAW,gBAAgB;IAC3B;;;;;;;;;OASG;IACH,iBAAiB,0CAA0C;IAC3D;;;;;;;;;OASG;IACH,iBAAiB,sBAAsB;IACvC;;;;;;;;;OASG;IACH,kBAAkB,uBAAuB;IACzC;;;;;;;;;;OAUG;IACH,cAAc,mBAAmB;IACjC;;;;;;;;;OASG;IACH,yBAAyB,8BAA8B;IACvD;;;;;;;;;OASG;IACH,YAAY,iBAAiB;IAC7B;;;;;;;;;;OAUG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;;;OASG;IACH,gBAAgB,qBAAqB;IACrC;;;;;;;;OAQG;IACH,cAAc,mBAAmB;IACjC;;;;;;;;;OASG;IACH,YAAY,iBAAiB;IAC7B;;;;;;;;OAQG;IACH,oBAAoB,yBAAyB;IAC7C;;;;;;;;OAQG;IACH,oBAAoB,yBAAyB;IAC7C;;;;;;;;;;OAUG;IACH,cAAc,mBAAmB;IACjC;;;;;;;;OAQG;IACH,eAAe,oBAAoB;IACnC;;;;;;;;OAQG;IACH,aAAa,kBAAkB;IAC/B;;OAEG;IACH,eAAe,oBAAoB;IACnC;;;;;;;;OAQG;IACH,2BAA2B,gCAAgC;IAC3D;;;;;;;;OAQG;IACH,mBAAmB,wBAAwB;IAC3C;;;;;;;;OAQG;IACH,cAAc,mBAAmB;IACjC;;;;;;;;OAQG;IACH,MAAM,WAAW;IACjB;;;;;;;;OAQG;IACH,8BAA8B,mCAAmC;IACjE;;;;;;;;;OASG;IACH,6BAA6B,kCAAkC;IAC/D;;;;;;;;OAQG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;;;;;OAWG;IACH,cAAc,mBAAmB;IACjC;;;;;;;;;;OAUG;IACH,kBAAkB,uBAAuB;IACzC;;;;;;;OAOG;IACH,gBAAgB,qBAAqB;IACrC;;;;;;;OAOG;IACH,SAAS,oBAAoB;IAC7B;;;;;;;;;OASG;IACH,aAAa,kBAAkB;IAC/B;;;;;;;;;;OAUG;IACH,yBAAyB,8BAA8B;IACvD;;;;;;;;OAQG;IACH,yBAAyB,8BAA4B;IACrD;;;;;;;;OAQG;IACH,cAAc,mBAAmB;IAEjC;;;;;;;;;;;;;;;;OAgBG;IACH,GAAG,QAAQ;IACX;;;;;;;;;;;OAWG;IACH,eAAe,oBAAoB;IAEnC;;;;;;;;OAQG;IACH,iBAAiB,sBAAsB;IAEvC;;;;;;;;;;;OAWG;IACH,KAAK,UAAU;IAEf;;;;;;;;OAQG;IACH,cAAc,mBAAmB;IAEjC;;;;;;;;OAQG;IACH,mBAAmB,wBAAwB;IAC3C;;;;;;;;;;;OAWG;IACH,kBAAkB,uBAAuB;IAEzC;;;;;;;;;OASG;IACH,iBAAiB,uBAAqB;IAEtC;;;;;;;;OAQG;IACH,YAAY,iBAAiB;IAE7B;;;;;;;;;;OAUG;IACH,oBAAoB,yBAAyB;IAE7C;;;;;;;;OAQG;IACH,qBAAqB,0BAA0B;IAE/C;;;;;;;;OAQG;IACH,uBAAuB,4BAA4B;IAEnD;;;;;;;;OAQG;IACH,2BAA2B,gCAAgC;IAE3D;;;;;;;OAOG;IACH,UAAU,eAAe;IAEzB;;;;;;;;OAQG;IACH,sBAAsB,2BAA2B;IAEjD;;;;;;;OAOG;IACH,cAAc,mBAAmB;IACjC;;;;;;;OAOG;IACH,kBAAkB,uBAAuB;IAEzC;;;;;;;;;OASG;IACH,2BAA2B,gCAAgC;IAE3D;;;;;;;;;OASG;IACH,qBAAqB,0BAA0B;IAE/C;;;;;;;;;OASG;IACH,kBAAkB,uBAAuB;IAEzC;;;;;;;;;OASG;IACH,gBAAgB,qBAAqB;IACrC;;;;;;;;;OASG;IACH,eAAe,oBAAoB;IACnC;;;;;;;;;OASG;IACH,kBAAkB,uBAAuB;IACzC;;;;;;;;;OASG;IACH,oBAAoB,yBAAyB;IAC7C;;;;;;;;OAQG;IACH,UAAU,eAAe;IACzB;;;;;;;OAOG;IACH,YAAY,iBAAiB;IAC7B;;;;;;;;OAQG;IACH,yBAAyB,8BAA8B;IACvD;;;;;;;;;;;;;OAaG;IACH,sBAAsB,2BAA2B;IACjD;;;;;;;;;OASG;IACH,qBAAqB,0BAA0B;IAC/C;;;;;;;;;OASG;IACH,uBAAuB,4BAA4B;IACnD;;;;;;;;;OASG;IACH,qBAAqB,0BAA0B;IAC/C;;;;;;;;;OASG;IACH,oBAAoB,yBAAyB;IAC7C;;;;;;;;;;OAUG;IACH,gBAAgB,qBAAqB;IACrC;;;;;;;;OAQG;IACH,mBAAmB,wBAAwB;IAC3C;;;;;;;;;;;OAWG;IACH,OAAO,YAAY;IACnB;;;;;;;;;;;OAWG;IACH,gBAAgB,qBAAqB;IACrC;;;;;;;;;;OAUG;IACH,SAAS,cAAc;IACvB;;;;;;;;;OASG;IACH,YAAY,iBAAiB;IAC7B;;;;;;;;OAQG;IACH,WAAW,gBAAgB;IAC3B;;;;;;;;OAQG;IACH,cAAc,mBAAmB;IACjC;;;;;;;;OAQG;IACH,WAAW,gBAAgB;IAC3B;;;;;;;;OAQG;IACH,qBAAqB,0BAA0B;IAC/C;;;;;;;;OAQG;IACH,oBAAoB,yBAAyB;IAC7C;;;;;;;;OAQG;IACH,oBAAoB,yBAAyB;IAC7C;;;;;;;;OAQG;IACH,oBAAoB,yBAAyB;IAC7C;;;;;;;;OAQG;IACH,cAAc,mBAAmB;IACjC;;;;;;;;OAQG;IACH,qBAAqB,0BAA0B;IAC/C;;;;;;;;OAQG;IACH,eAAe,oBAAoB;IACnC;;;;;;;;OAQG;IACH,iBAAiB,sBAAsB;IACvC;;;;;;;;OAQG;IACH,iBAAiB,sBAAsB;IACvC;;;;;;;;OAQG;IACH,WAAW,gBAAgB;IAC3B;;;;;;;;;OASG;IACH,6BAA6B,kCAAkC;IAC/D;;;;;;;;;OASG;IACH,qBAAqB,0BAA0B;IAC/C;;;;;;;;;OASG;IACH,uBAAuB,4BAA4B;IACnD;;;;;;;;;;OAUG;IACH,oBAAoB,yBAAyB;IAC7C;;;;;;;;;;OAUG;IACH,qBAAqB,0BAA0B;IAE/C;;;;;;;;;OASG;IACH,kBAAkB,uBAAuB;IAEzC;;;;;;;;;OASG;IACH,2BAA2B,gCAAgC;IAE3D;;;;;;;;;OASG;IACH,UAAU,eAAe;IACzB;;;;;;;;;OASG;IACH,4BAA4B,iCAAiC;CAChE;AACD,MAAM,WAAW,iBAAiB;IAC9B,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,GAAG,CAAC;CAC1D;AAED,oBAAY,gBAAgB;IACxB,OAAO,YAAY;IACnB,WAAW,gBAAgB;IAC3B,cAAc,mBAAmB;IACjC,QAAQ,aAAa;CACxB;AAED;;;GAGG;AACH,oBAAY,yBAAyB;IACjC,UAAU,eAAe;IACzB,WAAW,gBAAgB;IAC3B,WAAW,gBAAgB;CAC9B;AAED,MAAM,WAAW,WAAW;IACxB,MAAM,EAAE;QACJ,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACtB,CAAC;IACF,KAAK,EACC,MAAM,GACN,MAAM,GACN,OAAO,GACP;QACI,CAAC,EAAE;YACC,CAAC,EAAE,MAAM,CAAC;YACV,CAAC,EAAE,MAAM,CAAC;SACb,CAAC;KACL,CAAC;CACX;AAED,MAAM,WAAW,QAAQ;IACrB,kBAAkB,EAAE,WAAW,EAAE,CAAC;IAClC,gBAAgB,EAAE,WAAW,EAAE,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAChC,iBAAiB,CAAC,EAAE;QAChB,YAAY,EAAE,QAAQ,CAAC;QACvB,cAAc,EAAE,QAAQ,EAAE,CAAC;KAC9B,CAAC;IACF,eAAe,EAAE;QACb,IAAI,EAAE,MAAM,CAAC;QACb,EAAE,EAAE,MAAM,CAAC;QACX,OAAO,EAAE;YACL,MAAM,EAAE;gBACJ,IAAI,EAAE,MAAM,CAAC;aAChB,CAAC;SACL,CAAC;QACF,OAAO,EAAE,GAAG,EAAE,CAAC;QACf,IAAI,EAAE,GAAG,EAAE,CAAC;QACZ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACtB,CAAC;IACF,OAAO,EAAE,gBAAgB,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,qBAAqB,CAAC;IAChC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,WAAW,CAAC,EAAE;QACV,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;QACxB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;IACF,YAAY,CAAC,EAAE;QACX,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;KAC/B,CAAC;IACF,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;GAGG;AACH,oBAAY,qBAAqB;IAC7B;;;OAGG;IACH,OAAO,YAAY;IACnB;;;OAGG;IACH,IAAI,SAAS;IACb;;;;;;OAMG;IACH,WAAW,gBAAgB;CAC9B;AAED;;;;;GAKG;AACH,oBAAY,kBAAkB;IAC1B;;;;;;;;OAQG;IACH,SAAS,cAAc;IACvB;;;;;;;;;;;;OAYG;IACH,GAAG,QAAQ;IACX;;;;;;;;;;;OAWG;IACH,MAAM,WAAW;IACjB;;;;;;;;;OASG;IACH,OAAO,YAAY;CACtB;AAED;;;;;GAKG;AAEH,oBAAY,QAAQ;IAChB;;;;;;;;;;;OAWG;IACH,MAAM,WAAW;IACjB;;;;;;;;;;OAUG;IACH,KAAK,UAAU;IACf;;;;;;;;;;OAUG;IACH,IAAI,SAAS;IACb;;;;;;;;;;;OAWG;IACH,IAAI,SAAS;IAEb;;;;;;;;;;;OAWG;IACH,KAAK,UAAU;IACf;;;;;;;;;;OAUG;IACH,KAAK,UAAU;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,oBAAY,iBAAiB;IACzB,uBAAuB;IACvB,GAAG,QAAQ;IACX,+BAA+B;IAC/B,gBAAgB,qBAAqB;IACrC,4CAA4C;IAC5C,OAAO,YAAY;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA8BK;AACL,oBAAY,eAAe;IACvB,+CAA+C;IAC/C,sBAAsB,2BAA2B;IAEjD,0DAA0D;IAC1D,oBAAoB,yBAAyB;IAE7C,gDAAgD;IAChD,0BAA0B,+BAA+B;IAEzD,8CAA8C;IAC9C,uBAAuB,4BAA4B;IAEnD,4CAA4C;IAC5C,UAAU,eAAe;IAEzB,4CAA4C;IAC5C,aAAa,kBAAkB;IAE/B,sCAAsC;IACtC,wBAAwB,6BAA6B;IAErD,kCAAkC;IAClC,YAAY,iBAAiB;IAE7B,0EAA0E;IAC1E,iBAAiB,sBAAsB;IAEvC,8CAA8C;IAC9C,yBAAyB,8BAA8B;IAEvD,uCAAuC;IACvC,gCAAgC,qCAAqC;IAErE,0DAA0D;IAC1D,oBAAoB,yBAAyB;IAE7C,4CAA4C;IAC5C,WAAW,gBAAgB;IAE3B,2CAA2C;IAC3C,qBAAqB,0BAA0B;IAE/C,6EAA6E;IAC7E,6BAA6B,kCAAkC;IAE/D,iEAAiE;IACjE,yBAAyB,8BAA8B;CAC1D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAiDM;AACN,MAAM,WAAW,sBAAsB;IACnC,sCAAsC;IACtC,SAAS,EAAE,iBAAiB,CAAC;IAC7B,iEAAiE;IACjE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC3B,kEAAkE;IAClE,IAAI,EAAE,eAAe,CAAC;IACtB,6DAA6D;IAC7D,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACtB;AAED;;;;;;;;;;;;;;;GAeG;AACH,oBAAY,WAAW;IACnB;;OAEG;IACH,MAAM,kBAAkB;IACxB;;OAEG;IACH,SAAS,cAAc;IACvB;;OAEG;IACH,MAAM,WAAW;IACjB;;OAEG;IACH,OAAO,YAAY;IAEnB;;OAEG;IACH,KAAK,UAAU;CAClB;AAED,MAAM,WAAW,kBAAkB;IAC/B,cAAc,EAAE,uBAAuB,CAAC;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,qBAAqB,EAAE,cAAc,EAAE,CAAC;IACxC,wBAAwB,EAAE,MAAM,EAAE,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAChC,sBAAsB,EAAE,MAAM,EAAE,CAAC;IACjC,iCAAiC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACvD,iBAAiB,EAAE,eAAe,EAAE,CAAC;IACrC,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,aAAa,EAAE,CAAC,MAAM,GAAG,kBAAkB,CAAC,EAAE,CAAC;IAC/C,sBAAsB,EAAC,OAAO,CAAC;IAC/B,6BAA6B,CAAC,EAAC,OAAO,CAAA;IACtC,eAAe,CAAC,EAAC,OAAO,CAAA;CAC3B;AAED;;GAEG;AACH,oBAAY,kBAAkB;IAC1B;;OAEG;IACH,UAAU,eAAe;IACzB;;OAEG;IACH,GAAG,QAAQ;IACX;;OAEG;IACH,aAAa,kBAAkB;CAClC;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC5B;;;;;;OAMG;IACH,oCAAoC,CAAC,EAAE,OAAO,CAAC;IAC/C;;;;;;;;;;;;;;OAcG;IACH,aAAa,CAAC,EAAE,CAAC,MAAM,GAAG,kBAAkB,CAAC,EAAE,CAAC;IAChD;;;;;;;;;;;;;;;OAeG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,SAAS;IACtB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC1B;;OAEG;IACH,KAAK,EAAE,KAAK,CAAC;QACT;;WAEG;QACH,IAAI,EAAE,MAAM,CAAC;QACb;;WAEG;QACH,IAAI,EAAE,WAAW,CAAC;QAClB;;WAEG;QACH,SAAS,EAAE,SAAS,CAAC;KACxB,CAAC,CAAC;IACH;;OAEG;IACH,cAAc,EAAE;QACZ;;WAEG;QACH,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,WAAW,CAAC;QAClB,SAAS,EAAE,SAAS,CAAC;KACxB,CAAC;CACL;AAKD,MAAM,WAAW,cAAc;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACpC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACjC,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;GAGG;AACH,oBAAY,uBAAuB;IAC/B,mBAAmB;IACnB,WAAW,iBAAiB;IAC5B,gBAAgB;IAChB,QAAQ,cAAc;IACtB,+BAA+B;IAC/B,oBAAoB,6BAA6B;IACjD,4BAA4B;IAC5B,iBAAiB,0BAA0B;IAC3C,eAAe;IACf,OAAO,aAAa;IACpB,mBAAmB;IACnB,UAAU,iBAAiB;CAC9B;AAED;;;GAGG;AACH,oBAAY,6BAA6B;IACrC,kBAAkB;IAClB,EAAE,OAAO;IACT,sBAAsB;IACtB,KAAK,WAAW;IAChB,eAAe;IACf,QAAQ,aAAa;IACrB,uBAAuB;IACvB,cAAc,qBAAqB;IACnC,kBAAkB;IAClB,UAAU,gBAAgB;IAC1B,gBAAgB;IAChB,QAAQ,cAAc;IACtB,mBAAmB;IACnB,WAAW,iBAAiB;IAC5B,gBAAgB;IAChB,QAAQ,cAAc;IACtB,+BAA+B;IAC/B,kBAAkB,0BAA0B;IAC5C,4BAA4B;IAC5B,eAAe,uBAAuB;IACtC,eAAe;IACf,OAAO,aAAa;IACpB,mBAAmB;IACnB,UAAU,iBAAiB;IAC3B,iBAAiB;IACjB,SAAS,eAAe;IACxB,cAAc;IACd,MAAM,YAAY;IAClB,kBAAkB;IAClB,SAAS,gBAAgB;CAC5B;AAED;;;GAGG;AACH,oBAAY,oBAAoB;IAC5B,6BAA6B;IAC7B,KAAK,UAAU;IACf,0BAA0B;IAC1B,QAAQ,aAAa;CACxB;AAED;;;GAGG;AACH,oBAAY,mCAAmC;IAC3C,6BAA6B;IAC7B,UAAU,gBAAgB;IAC1B,8BAA8B;IAC9B,WAAW,iBAAiB;IAC5B,iCAAiC;IACjC,cAAc,oBAAoB;CACrC;AAED;;;GAGG;AACH,MAAM,WAAW,wBAAwB;IACrC,0BAA0B;IAC1B,QAAQ,EAAE,6BAA6B,GAAG,MAAM,CAAC;IACjD,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,WAAW,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,yCAAyC;IACzC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iDAAiD;IACjD,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,wEAAwE;IACxE,cAAc,CAAC,EAAE,mCAAmC,GAAG,MAAM,CAAC;IAC9D,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sCAAsC;IACtC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,2DAA2D;IAC3D,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,sDAAsD;IACtD,oBAAoB,CAAC,EAAE,oBAAoB,GAAG,MAAM,CAAC;IACrD,wCAAwC;IACxC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C,qCAAqC;IACrC,uBAAuB,CAAC,EAAE,uBAAuB,CAAC;CACrD;AAED;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IAClC,4CAA4C;IAC5C,IAAI,CAAC,EAAE,wBAAwB,EAAE,CAAC;CACrC;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IACzB,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;;GAGG;AACH,oBAAY,cAAc;IACtB,iCAAiC;IACjC,GAAG,QAAQ;IACX,oCAAoC;IACpC,MAAM,WAAW;IACjB,kCAAkC;IAClC,IAAI,SAAS;IACb,mCAAmC;IACnC,KAAK,UAAU;CAClB;AAED;;;GAGG;AACH,oBAAY,UAAU;IAClB,oBAAoB;IACpB,OAAO,YAAY;IACnB,gBAAgB;IAChB,GAAG,QAAQ;IACX,kBAAkB;IAClB,KAAK,UAAU;CAClB;AAED;;;GAGG;AACH,oBAAY,mBAAmB;IAC3B,sBAAsB;IACtB,OAAO,YAAY;IACnB,sBAAsB;IACtB,OAAO,YAAY;CACtB;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IACxB,8BAA8B;IAC9B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,cAAc,GAAG,MAAM,CAAC;IACnC,6CAA6C;IAC7C,YAAY,CAAC,EAAE,YAAY,CAAC;CAC/B;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC5B,6BAA6B;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sBAAsB;IACtB,QAAQ,CAAC,EAAE,uBAAuB,GAAG,MAAM,CAAC;CAC/C;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC5B,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qCAAqC;IACrC,MAAM,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;CACnC;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC3B,sCAAsC;IACtC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,qCAAqC;IACrC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,2CAA2C;IAC3C,eAAe,CAAC,EAAE,eAAe,EAAE,CAAC;CACvC;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC3B,sBAAsB;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,yBAAyB;IACzB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,4BAA4B;IAC5B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,+BAA+B;IAC/B,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACnC;AAED;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACrB,8BAA8B;IAC9B,CAAC,CAAC,EAAE,OAAO,CAAC;IACZ,gCAAgC;IAChC,CAAC,CAAC,EAAE,OAAO,CAAC;CACf;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IACzB,uCAAuC;IACvC,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,oCAAoC;IACpC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACvB;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACvB,+BAA+B;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACtB,wCAAwC;IACxC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,yBAAyB;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iCAAiC;IACjC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,iCAAiC;IACjC,UAAU,CAAC,EAAE,UAAU,CAAC;CAC3B;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IACxB,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;CACjD;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC3B,2BAA2B;IAC3B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,+BAA+B;IAC/B,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,iEAAiE;IACjE,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,8BAA8B;IAC9B,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC;IACnB,gCAAgC;IAChC,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,4CAA4C;IAC5C,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IACxB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;OAEG;IACH,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;CACjD;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IACzB,kBAAkB;IAClB,UAAU,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IACjC,4BAA4B;IAC5B,mBAAmB,CAAC,EAAE,mBAAmB,GAAG,MAAM,CAAC;CACtD;AAED;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACpC,kDAAkD;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,OAAO,EAAE,OAAO,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACjC,2CAA2C;IAC3C,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,8CAA8C;IAC9C,gBAAgB,CAAC,EAAE,uBAAuB,EAAE,CAAC;CAChD;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC3B,qEAAqE;IACrE,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,gDAAgD;IAChD,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,uDAAuD;IACvD,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C,4CAA4C;IAC5C,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,sBAAsB;IACnC,oCAAoC;IACpC,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,oCAAoC;IACpC,KAAK,CAAC,EAAE,cAAc,CAAC;CAC1B"} \ No newline at end of file diff --git a/dist/src/utils.d.ts b/dist/src/utils.d.ts new file mode 100644 index 00000000..c76bbeca --- /dev/null +++ b/dist/src/utils.d.ts @@ -0,0 +1,199 @@ +/** + * Copyright (c) 2023 + * + * Common utility functions for ThoughtSpot Visual Embed SDK + * @summary Utils + * @author Ayon Ghosh + */ +import { EmbedConfig, QueryParams, RuntimeFilter, CustomisationsInterface, DOMSelector, RuntimeParameter, AllEmbedViewConfig, BaseViewConfig } from './types'; +/** + * Construct a runtime filters query string from the given filters. + * Refer to the following docs for more details on runtime filter syntax: + * https://cloud-docs.thoughtspot.com/admin/ts-cloud/apply-runtime-filter.html + * https://cloud-docs.thoughtspot.com/admin/ts-cloud/runtime-filter-operators.html + * @param runtimeFilters + */ +export declare const getFilterQuery: (runtimeFilters: RuntimeFilter[]) => string | null; +/** + * Construct a runtime parameter override query string from the given option. + * @param runtimeParameters + */ +export declare const getRuntimeParameters: (runtimeParameters: RuntimeParameter[]) => string; +/** + * Return a query param string composed from the given params object + * @param queryParams + * @param shouldSerializeParamValues + */ +export declare const getQueryParamString: (queryParams: QueryParams, shouldSerializeParamValues?: boolean) => string; +/** + * Get a string representation of a dimension value in CSS + * If numeric, it is considered in pixels. + * @param value + */ +export declare const getCssDimension: (value: number | string) => string; +/** + * Validates if a string is a valid CSS margin value. + * @param value - The string to validate + * @returns true if the value is a valid CSS margin value, false otherwise + */ +export declare const isValidCssMargin: (value: string) => boolean; +export declare const getSSOMarker: (markerId: string) => string; +/** + * Append a string to a URL's hash fragment + * @param url A URL + * @param stringToAppend The string to append to the URL hash + */ +export declare const appendToUrlHash: (url: string, stringToAppend: string) => string; +/** + * + * @param url + * @param stringToAppend + * @param path + */ +export declare function getRedirectUrl(url: string, stringToAppend: string, path?: string): string; +export declare const getEncodedQueryParamsString: (queryString: string) => string; +export declare const getOffsetTop: (element: any) => any; +export declare const embedEventStatus: { + START: string; + END: string; +}; +export declare const setAttributes: (element: HTMLElement, attributes: { + [key: string]: string | number | boolean; +}) => void; +export declare const checkReleaseVersionInBeta: (releaseVersion: string, suppressBetaWarning: boolean) => boolean; +export declare const getCustomisations: (embedConfig: EmbedConfig, viewConfig: AllEmbedViewConfig) => CustomisationsInterface; +export declare const getRuntimeFilters: (runtimefilters: any) => string; +/** + * Gets a reference to the DOM node given + * a selector. + * @param domSelector + */ +export declare function getDOMNode(domSelector: DOMSelector): HTMLElement; +export declare const deepMerge: (target: any, source: any) => { + [x: string]: any; +}; +export declare const getOperationNameFromQuery: (query: string) => string; +/** + * + * @param obj + */ +export declare function removeTypename(obj: any): any; +/** + * Sets the specified style properties on an HTML element. + * @param {HTMLElement} element - The HTML element to which the styles should be applied. + * @param {Partial} styleProperties - An object containing style + * property names and their values. + * @example + * // Apply styles to an element + * const element = document.getElementById('myElement'); + * const styles = { + * backgroundColor: 'red', + * fontSize: '16px', + * }; + * setStyleProperties(element, styles); + */ +export declare const setStyleProperties: (element: HTMLElement, styleProperties: Partial) => void; +/** + * Removes specified style properties from an HTML element. + * @param {HTMLElement} element - The HTML element from which the styles should be removed. + * @param {string[]} styleProperties - An array of style property names to be removed. + * @example + * // Remove styles from an element + * const element = document.getElementById('myElement'); + * element.style.backgroundColor = 'red'; + * const propertiesToRemove = ['backgroundColor']; + * removeStyleProperties(element, propertiesToRemove); + */ +export declare const removeStyleProperties: (element: HTMLElement, styleProperties: string[]) => void; +export declare const isUndefined: (value: any) => boolean; +export declare const getTypeFromValue: (value: any) => [string, string]; +/** + * Stores a value in the global `window` object under the `_tsEmbedSDK` namespace. + * @param key - The key under which the value will be stored. + * @param value - The value to store. + * @param options - Additional options. + * @param options.ignoreIfAlreadyExists - Does not set if value for key is set. + * + * @returns The stored value. + * + * @version SDK: 1.36.2 | ThoughtSpot: * + */ +export declare function storeValueInWindow(key: string, value: T, options?: { + ignoreIfAlreadyExists?: boolean; +}): T; +/** + * Retrieves a stored value from the global + * `window` object under the `_tsEmbedSDK` namespace. + * Returns undefined in SSR environment. + */ +export declare const getValueFromWindow: (key: string) => T; +/** + * Check if an array includes a string value + * @param arr - The array to check + * @param key - The string to search for + * @returns boolean indicating if the string is found in the array + */ +export declare const arrayIncludesString: (arr: readonly unknown[], key: string) => boolean; +/** + * Resets the key if it exists in the `window` object under the `_tsEmbedSDK` key. + * Returns true if the key was reset, false otherwise. + * @param key - Key to reset + * @returns - boolean indicating if the key was reset + */ +export declare function resetValueFromWindow(key: string): boolean; +/** + * Handle Present HostEvent by entering fullscreen mode + * @param iframe The iframe element to make fullscreen + */ +export declare const handlePresentEvent: (iframe: HTMLIFrameElement) => Promise; +/** + * Handle ExitPresentMode EmbedEvent by exiting fullscreen mode + */ +export declare const handleExitPresentMode: () => Promise; +export declare const calculateVisibleElementData: (element: HTMLElement) => { + top: number; + height: number; + left: number; + width: number; +}; +/** + * Replaces placeholders in a template string with provided values. + * Placeholders should be in the format {key}. + * @param template - The template string with placeholders + * @param values - An object containing key-value pairs to replace placeholders + * @returns The template string with placeholders replaced + * @example + * formatTemplate('Hello {name}, you are {age} years old', { name: 'John', age: 30 }) + * // Returns: 'Hello John, you are 30 years old' + * + * formatTemplate('Expected {type}, but received {actual}', { type: 'string', actual: 'number' }) + * // Returns: 'Expected string, but received number' + */ +export declare const formatTemplate: (template: string, values: Record) => string; +export declare const getHostEventsConfig: (viewConfig: BaseViewConfig) => { + shouldBypassPayloadValidation: boolean; + useHostEventsV2: boolean; +}; +/** + * Check if the window is undefined + * If the window is undefined, it means the code is running in a SSR environment. + * @returns true if the window is undefined, false otherwise + * + */ +export declare const isWindowUndefined: () => boolean; +/** + * Validates that a URL uses only http: or https: protocols. + * Returns a tuple of [isValid, error] so the caller can handle validation errors. + * @param url - The URL string to validate + * @returns [true, null] if valid, [false, Error] if invalid + */ +export declare const validateHttpUrl: (url: string) => [boolean, Error | null]; +/** + * Sets a query parameter if the value is defined. + * @param queryParams - The query params object to modify + * @param param - The parameter key + * @param value - The value to set + * @param asBoolean - If true, coerces value to boolean + */ +export declare const setParamIfDefined: (queryParams: Record, param: string, value: T, asBoolean?: boolean) => void; +//# sourceMappingURL=utils.d.ts.map \ No newline at end of file diff --git a/dist/src/utils.d.ts.map b/dist/src/utils.d.ts.map new file mode 100644 index 00000000..d3a9f3ce --- /dev/null +++ b/dist/src/utils.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EACH,WAAW,EACX,WAAW,EACX,aAAa,EACb,uBAAuB,EACvB,WAAW,EACX,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACjB,MAAM,SAAS,CAAC;AAIjB;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,mBAAoB,aAAa,EAAE,KAAG,MAAM,GAAG,IAqBzE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,sBAAuB,gBAAgB,EAAE,KAAG,MAe5E,CAAC;AAwBF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,gBACf,WAAW,2CAEzB,MAkBF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,UAAW,MAAM,GAAG,MAAM,KAAG,MAMxD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,UAAW,MAAM,KAAG,OA4BhD,CAAC;AAEF,eAAO,MAAM,YAAY,aAAc,MAAM,WAG5C,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,QAAS,MAAM,kBAAkB,MAAM,WAkBlE,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,IAAI,SAAK,UAG5E;AAED,eAAO,MAAM,2BAA2B,gBAAiB,MAAM,WAK9D,CAAC;AAEF,eAAO,MAAM,YAAY,YAAa,GAAG,QAGxC,CAAC;AAEF,eAAO,MAAM,gBAAgB;;;CAG5B,CAAC;AAEF,eAAO,MAAM,aAAa,YACb,WAAW;;MAErB,IAIF,CAAC;AAKF,eAAO,MAAM,yBAAyB,mBAClB,MAAM,uBACD,OAAO,KAC7B,OAQF,CAAC;AAEF,eAAO,MAAM,iBAAiB,gBACb,WAAW,cACZ,kBAAkB,KAC/B,uBAuBF,CAAC;AAEF,eAAO,MAAM,iBAAiB,mBAAoB,GAAG,WAAyC,CAAC;AAE/F;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,WAAW,GAAG,WAAW,CAEhE;AAED,eAAO,MAAM,SAAS,WAAY,GAAG,UAAU,GAAG;;CAA0B,CAAC;AAE7E,eAAO,MAAM,yBAAyB,UAAW,MAAM,WAItD,CAAC;AAEF;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,OAYtC;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,kBAAkB,YAClB,WAAW,mBACH,QAAQ,mBAAmB,CAAC,KAC9C,IASF,CAAC;AACF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB,YAAa,WAAW,mBAAmB,MAAM,EAAE,KAAG,IAKvF,CAAC;AAEF,eAAO,MAAM,WAAW,UAAW,GAAG,KAAG,OAA8B,CAAC;AAGxE,eAAO,MAAM,gBAAgB,UAAW,GAAG,KAAG,CAAC,MAAM,EAAE,MAAM,CAW5D,CAAC;AAIF;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAChC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,CAAC,EACR,OAAO,GAAE;IAAE,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAAO,GAClD,CAAC,CAYH;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,iBAAkB,MAAM,MAGtD,CAAC;AACF;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,QAAS,SAAS,OAAO,EAAE,OAAO,MAAM,KAAG,OAE1E,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOzD;AAcD;;;GAGG;AACH,eAAO,MAAM,kBAAkB,WAAkB,iBAAiB,KAAG,QAAQ,IAAI,CA0BhF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,QAAa,QAAQ,IAAI,CA0B1D,CAAC;AAEF,eAAO,MAAM,2BAA2B,YAAa,WAAW;;;;;CAoB/D,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc,aAAc,MAAM,UAAU,OAAO,MAAM,EAAE,GAAG,CAAC,KAAG,MAM9E,CAAC;AAEF,eAAO,MAAM,mBAAmB,eAAgB,cAAc;;;CAK7D,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,QAAO,OAMpC,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,eAAe,QAAS,MAAM,KAAG,CAAC,OAAO,EAAE,KAAK,GAAG,IAAI,CAUnE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,mBACb,OAAO,MAAM,EAAE,OAAO,CAAC,SAC7B,MAAM,oCAGd,IAIF,CAAC"} \ No newline at end of file diff --git a/dist/src/utils.spec.d.ts b/dist/src/utils.spec.d.ts new file mode 100644 index 00000000..d677f33d --- /dev/null +++ b/dist/src/utils.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=utils.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/utils.spec.d.ts.map b/dist/src/utils.spec.d.ts.map new file mode 100644 index 00000000..c87e40d7 --- /dev/null +++ b/dist/src/utils.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"utils.spec.d.ts","sourceRoot":"","sources":["utils.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/utils/authService/authService.d.ts b/dist/src/utils/authService/authService.d.ts new file mode 100644 index 00000000..cb2d0ecc --- /dev/null +++ b/dist/src/utils/authService/authService.d.ts @@ -0,0 +1,46 @@ +export declare const EndPoints: { + AUTH_VERIFICATION: string; + SESSION_INFO: string; + PREAUTH_INFO: string; + SAML_LOGIN_TEMPLATE: (targetUrl: string) => string; + OIDC_LOGIN_TEMPLATE: (targetUrl: string) => string; + TOKEN_LOGIN: string; + BASIC_LOGIN: string; + LOGOUT: string; + EXECUTE_TML: string; + EXPORT_TML: string; + IS_ACTIVE: string; +}; +/** + * Service to validate a auth token against a ThoughtSpot host. + * @param thoughtSpotHost : ThoughtSpot host to verify the token against. + * @param authToken : Auth token to verify. + */ +export declare function verifyTokenService(thoughtSpotHost: string, authToken: string): Promise; +/** + * + * @param authEndpoint + */ +export declare function fetchAuthTokenService(authEndpoint: string): Promise; +/** + * + * @param thoughtSpotHost + * @param username + * @param authToken + */ +export declare function fetchAuthService(thoughtSpotHost: string, username: string, authToken: string): Promise; +/** + * + * @param thoughtSpotHost + * @param username + * @param authToken + */ +export declare function fetchAuthPostService(thoughtSpotHost: string, username: string, authToken: string): Promise; +/** + * + * @param thoughtSpotHost + * @param username + * @param password + */ +export declare function fetchBasicAuthService(thoughtSpotHost: string, username: string, password: string): Promise; +//# sourceMappingURL=authService.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/authService/authService.d.ts.map b/dist/src/utils/authService/authService.d.ts.map new file mode 100644 index 00000000..22e2d756 --- /dev/null +++ b/dist/src/utils/authService/authService.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"authService.d.ts","sourceRoot":"","sources":["authService.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,SAAS;;;;qCAIe,MAAM;qCACN,MAAM;;;;;;;CAO1C,CAAC;AAgBF;;;;GAIG;AACH,wBAAsB,kBAAkB,CACpC,eAAe,EAAE,MAAM,EACvB,SAAS,EAAE,MAAM,GAClB,OAAO,CAAC,OAAO,CAAC,CAgBlB;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAE9E;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAClC,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAClB,OAAO,CAAC,GAAG,CAAC,CAUd;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACtC,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAClB,OAAO,CAAC,GAAG,CAAC,CAgBd;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACvC,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACjB,OAAO,CAAC,GAAG,CAAC,CAUd"} \ No newline at end of file diff --git a/dist/src/utils/authService/authService.spec.d.ts b/dist/src/utils/authService/authService.spec.d.ts new file mode 100644 index 00000000..819cc724 --- /dev/null +++ b/dist/src/utils/authService/authService.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=authService.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/authService/authService.spec.d.ts.map b/dist/src/utils/authService/authService.spec.d.ts.map new file mode 100644 index 00000000..5ee5e27c --- /dev/null +++ b/dist/src/utils/authService/authService.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"authService.spec.d.ts","sourceRoot":"","sources":["authService.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/utils/authService/index.d.ts b/dist/src/utils/authService/index.d.ts new file mode 100644 index 00000000..2dbe1ac7 --- /dev/null +++ b/dist/src/utils/authService/index.d.ts @@ -0,0 +1,3 @@ +export { EndPoints, fetchAuthPostService, fetchAuthService, fetchAuthTokenService, fetchBasicAuthService, verifyTokenService, } from './authService'; +export { fetchLogoutService, fetchSessionInfoService, fetchPreauthInfoService, } from './tokenizedAuthService'; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/authService/index.d.ts.map b/dist/src/utils/authService/index.d.ts.map new file mode 100644 index 00000000..652dafe3 --- /dev/null +++ b/dist/src/utils/authService/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,SAAS,EACT,oBAAoB,EACpB,gBAAgB,EAChB,qBAAqB,EACrB,qBAAqB,EACrB,kBAAkB,GACrB,MAAM,eAAe,CAAC;AACvB,OAAO,EACH,kBAAkB,EAClB,uBAAuB,EACvB,uBAAuB,GAC1B,MAAM,wBAAwB,CAAC"} \ No newline at end of file diff --git a/dist/src/utils/authService/tokenizedAuthService.d.ts b/dist/src/utils/authService/tokenizedAuthService.d.ts new file mode 100644 index 00000000..9b00124b --- /dev/null +++ b/dist/src/utils/authService/tokenizedAuthService.d.ts @@ -0,0 +1,32 @@ +/** + * Fetches the session info from the ThoughtSpot server. + * @param thoughtspotHost + * @returns {Promise} + * @example + * ```js + * const response = await sessionInfoService(); + * ``` + */ +export declare function fetchPreauthInfoService(thoughtspotHost: string): Promise; +/** + * Fetches the session info from the ThoughtSpot server. + * @param thoughtspotHost + * @returns {Promise} + * @example + * ```js + * const response = await sessionInfoService(); + * ``` + */ +export declare function fetchSessionInfoService(thoughtspotHost: string): Promise; +/** + * + * @param thoughtSpotHost + */ +export declare function fetchLogoutService(thoughtSpotHost: string): Promise; +/** + * Is active service to check if the user is logged in. + * @param thoughtSpotHost + * @version SDK: 1.28.4 | ThoughtSpot: * + */ +export declare function isActiveService(thoughtSpotHost: string): Promise; +//# sourceMappingURL=tokenizedAuthService.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/authService/tokenizedAuthService.d.ts.map b/dist/src/utils/authService/tokenizedAuthService.d.ts.map new file mode 100644 index 00000000..c31e0980 --- /dev/null +++ b/dist/src/utils/authService/tokenizedAuthService.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"tokenizedAuthService.d.ts","sourceRoot":"","sources":["tokenizedAuthService.ts"],"names":[],"mappings":"AAkBA;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAenF;AAED;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAQnF;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAQ9E;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAY/E"} \ No newline at end of file diff --git a/dist/src/utils/authService/tokenizedAuthService.spec.d.ts b/dist/src/utils/authService/tokenizedAuthService.spec.d.ts new file mode 100644 index 00000000..5d7f3d3c --- /dev/null +++ b/dist/src/utils/authService/tokenizedAuthService.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=tokenizedAuthService.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/authService/tokenizedAuthService.spec.d.ts.map b/dist/src/utils/authService/tokenizedAuthService.spec.d.ts.map new file mode 100644 index 00000000..28c3d725 --- /dev/null +++ b/dist/src/utils/authService/tokenizedAuthService.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"tokenizedAuthService.spec.d.ts","sourceRoot":"","sources":["tokenizedAuthService.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/utils/custom-actions.d.ts b/dist/src/utils/custom-actions.d.ts new file mode 100644 index 00000000..48d0ed55 --- /dev/null +++ b/dist/src/utils/custom-actions.d.ts @@ -0,0 +1,12 @@ +import { CustomAction } from '../types'; +export interface CustomActionsValidationResult { + actions: CustomAction[]; + errors: string[]; +} +/** + * Validates and processes custom actions + * @param customActions - Array of custom actions to validate + * @returns Object containing valid actions and any validation errors + */ +export declare const getCustomActions: (customActions: CustomAction[]) => CustomActionsValidationResult; +//# sourceMappingURL=custom-actions.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/custom-actions.d.ts.map b/dist/src/utils/custom-actions.d.ts.map new file mode 100644 index 00000000..b33dca7a --- /dev/null +++ b/dist/src/utils/custom-actions.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"custom-actions.d.ts","sourceRoot":"","sources":["custom-actions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA6C,MAAM,UAAU,CAAC;AAMnF,MAAM,WAAW,6BAA6B;IAC1C,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,MAAM,EAAE,MAAM,EAAE,CAAC;CACpB;AA6JD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,kBAAmB,YAAY,EAAE,KAAG,6BAwDhE,CAAC"} \ No newline at end of file diff --git a/dist/src/utils/custom-actions.spec.d.ts b/dist/src/utils/custom-actions.spec.d.ts new file mode 100644 index 00000000..2d0239da --- /dev/null +++ b/dist/src/utils/custom-actions.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=custom-actions.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/custom-actions.spec.d.ts.map b/dist/src/utils/custom-actions.spec.d.ts.map new file mode 100644 index 00000000..642333d3 --- /dev/null +++ b/dist/src/utils/custom-actions.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"custom-actions.spec.d.ts","sourceRoot":"","sources":["custom-actions.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/utils/global-styles.d.ts b/dist/src/utils/global-styles.d.ts new file mode 100644 index 00000000..65c12d89 --- /dev/null +++ b/dist/src/utils/global-styles.d.ts @@ -0,0 +1,2 @@ +export declare const addPreviewStylesIfNotPresent: () => void; +//# sourceMappingURL=global-styles.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/global-styles.d.ts.map b/dist/src/utils/global-styles.d.ts.map new file mode 100644 index 00000000..a04eace0 --- /dev/null +++ b/dist/src/utils/global-styles.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"global-styles.d.ts","sourceRoot":"","sources":["global-styles.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,4BAA4B,YAuCxC,CAAC"} \ No newline at end of file diff --git a/dist/src/utils/global-styles.spec.d.ts b/dist/src/utils/global-styles.spec.d.ts new file mode 100644 index 00000000..d1038965 --- /dev/null +++ b/dist/src/utils/global-styles.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=global-styles.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/global-styles.spec.d.ts.map b/dist/src/utils/global-styles.spec.d.ts.map new file mode 100644 index 00000000..34626509 --- /dev/null +++ b/dist/src/utils/global-styles.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"global-styles.spec.d.ts","sourceRoot":"","sources":["global-styles.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/utils/graphql/answerService/answer-queries.d.ts b/dist/src/utils/graphql/answerService/answer-queries.d.ts new file mode 100644 index 00000000..6c069597 --- /dev/null +++ b/dist/src/utils/graphql/answerService/answer-queries.d.ts @@ -0,0 +1,11 @@ +export declare const getUnaggregatedAnswerSession: string; +export declare const removeColumns: string; +export declare const addColumns: string; +export declare const addFilter: string; +export declare const getAnswer: string; +export declare const getAnswerData: string; +export declare const addVizToLiveboard: string; +export declare const getSQLQuery = "\n mutation GetSQLQuery($session: BachSessionIdInput!) {\n Answer__getQuery(session: $session) {\n sql\n }\n }\n"; +export declare const updateDisplayMode = "\n mutation UpdateDisplayMode(\n $session: BachSessionIdInput!\n $displayMode: DisplayMode\n) {\n Answer__updateProperties(session: $session, displayMode: $displayMode) {\n id {\n sessionId\n genNo\n acSession {\n sessionId\n genNo\n }\n }\n answer {\n id\n displayMode\n suggestedDisplayMode\n }\n }\n}\n"; +export declare const getAnswerTML = "\nmutation GetUnsavedAnswerTML($session: BachSessionIdInput!, $exportDependencies: Boolean, $formatType: EDocFormatType, $exportPermissions: Boolean, $exportFqn: Boolean) {\n UnsavedAnswer_getTML(\n session: $session\n exportDependencies: $exportDependencies\n formatType: $formatType\n exportPermissions: $exportPermissions\n exportFqn: $exportFqn\n ) {\n zipFile\n object {\n edoc\n name\n type\n __typename\n }\n __typename\n }\n}"; +//# sourceMappingURL=answer-queries.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/graphql/answerService/answer-queries.d.ts.map b/dist/src/utils/graphql/answerService/answer-queries.d.ts.map new file mode 100644 index 00000000..2affa580 --- /dev/null +++ b/dist/src/utils/graphql/answerService/answer-queries.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"answer-queries.d.ts","sourceRoot":"","sources":["answer-queries.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,4BAA4B,QAsBxC,CAAC;AAEF,eAAO,MAAM,aAAa,QAUrB,CAAC;AAEN,eAAO,MAAM,UAAU,QAMlB,CAAC;AAEN,eAAO,MAAM,SAAS,QAMrB,CAAC;AAEF,eAAO,MAAM,SAAS,QA+DrB,CAAC;AAEF,eAAO,MAAM,aAAa,QAwBzB,CAAC;AAEF,eAAO,MAAM,iBAAiB,QAc7B,CAAC;AAEF,eAAO,MAAM,WAAW,oJAMvB,CAAC;AAEF,eAAO,MAAM,iBAAiB,2cAqB7B,CAAC;AAEF,eAAO,MAAM,YAAY,yeAkBvB,CAAC"} \ No newline at end of file diff --git a/dist/src/utils/graphql/answerService/answerService.d.ts b/dist/src/utils/graphql/answerService/answerService.d.ts new file mode 100644 index 00000000..ab516b5d --- /dev/null +++ b/dist/src/utils/graphql/answerService/answerService.d.ts @@ -0,0 +1,207 @@ +import type { RuntimeFilter, RuntimeFilterOp, VizPoint } from '../../../types'; +export interface SessionInterface { + sessionId: string; + genNo: number; + acSession: { + sessionId: string; + genNo: number; + }; +} +export declare enum OperationType { + GetChartWithData = "GetChartWithData", + GetTableWithHeadlineData = "GetTableWithHeadlineData" +} +export interface UnderlyingDataPoint { + columnId: string; + dataValue: any; +} +export declare const DATA_TYPES: string[]; +/** + * AnswerService provides a simple way to work with ThoughtSpot Answers. + * + * This service allows you to interact with ThoughtSpot Answers programmatically, + * making it easy to customize visualizations, filter data, and extract insights + * directly from your application. + * + * You can use this service to: + * + * - Add or remove columns from Answers (`addColumns`, `removeColumns`, + * `addColumnsByName`) + * - Apply filters to Answers (`addFilter`) + * - Get data from Answers in different formats (JSON, CSV, PNG) (`fetchData`, + * `fetchCSVBlob`, `fetchPNGBlob`) + * - Get data for specific points in visualizations + * (`getUnderlyingDataForPoint`) + * - Run custom queries (`executeQuery`) + * - Add visualizations to Liveboards (`addDisplayedVizToLiveboard`) + * + * @example + * ```js + * // Get the answer service + * embed.on(EmbedEvent.Data, async (e) => { + * const service = await embed.getAnswerService(); + * + * // Add columns to the answer + * await service.addColumnsByName(["Sales", "Region"]); + * + * // Get the data + * const data = await service.fetchData(); + * console.log(data); + * }); + * ``` + * + * @example + * ```js + * // Get data for a point in a visualization + * embed.on(EmbedEvent.CustomAction, async (e) => { + * const underlying = await e.answerService.getUnderlyingDataForPoint([ + * 'Product Name', + * 'Sales Amount' + * ]); + * + * const data = await underlying.fetchData(0, 100); + * console.log(data); + * }); + * ``` + * + * @version SDK: 1.25.0 | ThoughtSpot: 9.10.0.cl + * @group Events + */ +export declare class AnswerService { + private session; + private thoughtSpotHost; + private selectedPoints?; + private answer; + private tmlOverride; + /** + * Should not need to be called directly. + * @param session + * @param answer + * @param thoughtSpotHost + * @param selectedPoints + */ + constructor(session: SessionInterface, answer: any, thoughtSpotHost: string, selectedPoints?: VizPoint[]); + /** + * Get the details about the source used in the answer. + * This can be used to get the list of all columns in the data source for example. + */ + getSourceDetail(): Promise; + /** + * Remove columnIds and return updated answer session. + * @param columnIds + * @returns + */ + removeColumns(columnIds: string[]): Promise; + /** + * Add columnIds and return updated answer session. + * @param columnIds + * @returns + */ + addColumns(columnIds: string[]): Promise; + /** + * Add columns by names and return updated answer session. + * @param columnNames + * @returns + * @example + * ```js + * embed.on(EmbedEvent.Data, async (e) => { + * const service = await embed.getAnswerService(); + * await service.addColumnsByName([ + * "col name 1", + * "col name 2" + * ]); + * console.log(await service.fetchData()); + * }); + * ``` + */ + addColumnsByName(columnNames: string[]): Promise; + /** + * Add a filter to the answer. + * @param columnName + * @param operator + * @param values + * @returns + */ + addFilter(columnName: string, operator: RuntimeFilterOp, values: RuntimeFilter['values']): Promise; + updateDisplayMode(displayMode?: string): Promise; + getSQLQuery(fetchSQLWithAllColumns?: boolean): Promise; + /** + * Fetch data from the answer. + * @param offset + * @param size + * @returns + */ + fetchData(offset?: number, size?: number): Promise<{ + columns: any; + data: any; + }>; + /** + * Fetch the data for the answer as a CSV blob. This might be + * quicker for larger data. + * @param userLocale + * @param includeInfo Include the CSV header in the output + * @returns Response + */ + fetchCSVBlob(userLocale?: string, includeInfo?: boolean): Promise; + /** + * Fetch the data for the answer as a PNG blob. This might be + * quicker for larger data. + * @param userLocale + * @param includeInfo + * @param omitBackground Omit the background in the PNG + * @param deviceScaleFactor The scale factor for the PNG + * @return Response + */ + fetchPNGBlob(userLocale?: string, omitBackground?: boolean, deviceScaleFactor?: number): Promise; + /** + * Just get the internal URL for this answer's data + * as a CSV blob. + * @param userLocale + * @param includeInfo + * @returns + */ + getFetchCSVBlobUrl(userLocale?: string, includeInfo?: boolean): string; + /** + * Just get the internal URL for this answer's data + * as a PNG blob. + * @param userLocale + * @param omitBackground + * @param deviceScaleFactor + */ + getFetchPNGBlobUrl(userLocale?: string, omitBackground?: boolean, deviceScaleFactor?: number): string; + /** + * Get underlying data given a point and the output column names. + * In case of a context menu action, the selectedPoints are + * automatically passed. + * @param outputColumnNames + * @param selectedPoints + * @example + * ```js + * embed.on(EmbedEvent.CustomAction, e => { + * const underlying = await e.answerService.getUnderlyingDataForPoint([ + * 'col name 1' // The column should exist in the data source. + * ]); + * const data = await underlying.fetchData(0, 100); + * }) + * ``` + * @version SDK: 1.25.0 | ThoughtSpot: 9.10.0.cl + */ + getUnderlyingDataForPoint(outputColumnNames: string[], selectedPoints?: UnderlyingDataPoint[]): Promise; + /** + * Execute a custom graphql query in the context of the answer. + * @param query graphql query + * @param variables graphql variables + * @returns + */ + executeQuery(query: string, variables: any): Promise; + /** + * Get the internal session details for the answer. + * @returns + */ + getSession(): SessionInterface; + getAnswer(): Promise; + getTML(): Promise; + addDisplayedVizToLiveboard(liveboardId: string): Promise; + setTMLOverride(override: any): void; +} +//# sourceMappingURL=answerService.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/graphql/answerService/answerService.d.ts.map b/dist/src/utils/graphql/answerService/answerService.d.ts.map new file mode 100644 index 00000000..92674d41 --- /dev/null +++ b/dist/src/utils/graphql/answerService/answerService.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"answerService.d.ts","sourceRoot":"","sources":["answerService.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACK,aAAa,EAAE,eAAe,EAAE,QAAQ,EACxD,MAAM,gBAAgB,CAAC;AAMxB,MAAM,WAAW,gBAAgB;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CACnD;AAED,oBAAY,aAAa;IACrB,gBAAgB,qBAAqB;IACrC,wBAAwB,6BAA6B;CACxD;AAED,MAAM,WAAW,mBAAmB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,GAAG,CAAC;CAClB;AAED,eAAO,MAAM,UAAU,UAAgC,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,qBAAa,aAAa;IAalB,OAAO,CAAC,OAAO;IAEf,OAAO,CAAC,eAAe;IACvB,OAAO,CAAC,cAAc,CAAC;IAf3B,OAAO,CAAC,MAAM,CAAe;IAE7B,OAAO,CAAC,WAAW,CAAM;IAEzB;;;;;;OAMG;gBAES,OAAO,EAAE,gBAAgB,EACjC,MAAM,EAAE,GAAG,EACH,eAAe,EAAE,MAAM,EACvB,cAAc,CAAC,EAAE,QAAQ,EAAE;IAMvC;;;OAGG;IACU,eAAe;IAQ5B;;;;OAIG;IACU,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE;IAS9C;;;;OAIG;IACU,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE;IAS3C;;;;;;;;;;;;;;;OAeG;IACU,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE;IAMnD;;;;;;OAMG;IACU,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC;IA2BxF,iBAAiB,CAAC,WAAW,SAAe;IAS5C,WAAW,CAAC,sBAAsB,UAAQ,GAAG,OAAO,CAAC,MAAM,CAAC;IAWzE;;;;;OAKG;IACU,SAAS,CAAC,MAAM,SAAI,EAAE,IAAI,SAAO;;;;IAqB9C;;;;;;OAMG;IACU,YAAY,CAAC,UAAU,SAAU,EAAE,WAAW,UAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAOvF;;;;;;;;OAQG;IACU,YAAY,CAAC,UAAU,SAAU,EAAE,cAAc,UAAQ,EAAE,iBAAiB,SAAI,GAAG,OAAO,CAAC,QAAQ,CAAC;IAWjH;;;;;;OAMG;IACI,kBAAkB,CAAC,UAAU,SAAU,EAAE,WAAW,UAAQ,GAAG,MAAM;IAI5E;;;;;;OAMG;IACI,kBAAkB,CAAC,UAAU,SAAU,EAAE,cAAc,UAAQ,EAAE,iBAAiB,SAAI,GAAG,MAAM;IAItG;;;;;;;;;;;;;;;;OAgBG;IACU,yBAAyB,CAClC,iBAAiB,EAAE,MAAM,EAAE,EAC3B,cAAc,CAAC,EAAE,mBAAmB,EAAE,GACvC,OAAO,CAAC,aAAa,CAAC;IA8CzB;;;;;OAKG;IACU,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAetE;;;OAGG;IACI,UAAU;IAIJ,SAAS;IAWT,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC;IAgBtB,0BAA0B,CAAC,WAAW,EAAE,MAAM;IAYpD,cAAc,CAAC,QAAQ,EAAE,GAAG;CAGtC"} \ No newline at end of file diff --git a/dist/src/utils/graphql/answerService/answerService.spec.d.ts b/dist/src/utils/graphql/answerService/answerService.spec.d.ts new file mode 100644 index 00000000..815e49e0 --- /dev/null +++ b/dist/src/utils/graphql/answerService/answerService.spec.d.ts @@ -0,0 +1,2 @@ +import 'jest-fetch-mock'; +//# sourceMappingURL=answerService.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/graphql/answerService/answerService.spec.d.ts.map b/dist/src/utils/graphql/answerService/answerService.spec.d.ts.map new file mode 100644 index 00000000..3d6edfd3 --- /dev/null +++ b/dist/src/utils/graphql/answerService/answerService.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"answerService.spec.d.ts","sourceRoot":"","sources":["answerService.spec.ts"],"names":[],"mappings":"AAAA,OAAO,iBAAiB,CAAC"} \ No newline at end of file diff --git a/dist/src/utils/graphql/graphql-request.d.ts b/dist/src/utils/graphql/graphql-request.d.ts new file mode 100644 index 00000000..1f493ea1 --- /dev/null +++ b/dist/src/utils/graphql/graphql-request.d.ts @@ -0,0 +1,15 @@ +/** + * + * @param root0 + * @param root0.query + * @param root0.variables + * @param root0.thoughtSpotHost + * @param root0.isCompositeQuery + */ +export declare function graphqlQuery({ query, variables, thoughtSpotHost, isCompositeQuery, }: { + query: string; + variables: any; + thoughtSpotHost: string; + isCompositeQuery?: boolean; +}): Promise; +//# sourceMappingURL=graphql-request.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/graphql/graphql-request.d.ts.map b/dist/src/utils/graphql/graphql-request.d.ts.map new file mode 100644 index 00000000..3dedd944 --- /dev/null +++ b/dist/src/utils/graphql/graphql-request.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"graphql-request.d.ts","sourceRoot":"","sources":["graphql-request.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,EAC/B,KAAK,EACL,SAAS,EACT,eAAe,EACf,gBAAwB,GAC3B,EAAE;IACC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,GAAG,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC7B,GAAG,OAAO,CAAC,GAAG,CAAC,CAwBf"} \ No newline at end of file diff --git a/dist/src/utils/graphql/graphql-request.spec.d.ts b/dist/src/utils/graphql/graphql-request.spec.d.ts new file mode 100644 index 00000000..9de32a2b --- /dev/null +++ b/dist/src/utils/graphql/graphql-request.spec.d.ts @@ -0,0 +1,2 @@ +import 'jest-fetch-mock'; +//# sourceMappingURL=graphql-request.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/graphql/graphql-request.spec.d.ts.map b/dist/src/utils/graphql/graphql-request.spec.d.ts.map new file mode 100644 index 00000000..c8529a00 --- /dev/null +++ b/dist/src/utils/graphql/graphql-request.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"graphql-request.spec.d.ts","sourceRoot":"","sources":["graphql-request.spec.ts"],"names":[],"mappings":"AAAA,OAAO,iBAAiB,CAAC"} \ No newline at end of file diff --git a/dist/src/utils/graphql/nlsService/conversation-queries.d.ts b/dist/src/utils/graphql/nlsService/conversation-queries.d.ts new file mode 100644 index 00000000..3cf88fb2 --- /dev/null +++ b/dist/src/utils/graphql/nlsService/conversation-queries.d.ts @@ -0,0 +1,3 @@ +export declare const createConversation = "\nmutation CreateConversation($params: Input_convassist_CreateConversationRequest) {\n ConvAssist__createConversation(request: $params) {\n convId\n initialCtx {\n type\n tsAnsCtx {\n sessionId\n genNo\n stateKey {\n transactionId\n generationNumber\n }\n worksheet {\n worksheetId\n worksheetName\n }\n }\n }\n }\n}\n"; +export declare const sendMessage = "\nquery SendMessage($params: Input_convassist_SendMessageRequest) {\n ConvAssist__sendMessage(request: $params) {\n responses {\n timestamp\n msgId\n data {\n asstRespData {\n tool\n asstRespText\n nlsAnsData {\n sageQuerySuggestions {\n sageQueryTokens {\n additions {\n phrase {\n isCompletePhrase\n numTokens\n phraseType\n startIndex\n __typename\n }\n tokens {\n token\n dataType\n typeEnum\n guid\n tokenMetadata {\n name\n __typename\n }\n __typename\n }\n __typename\n }\n phrases {\n isCompletePhrase\n numTokens\n phraseType\n startIndex\n __typename\n }\n removals {\n phrase {\n isCompletePhrase\n numTokens\n phraseType\n startIndex\n __typename\n }\n tokens {\n token\n dataType\n typeEnum\n guid\n tokenMetadata {\n name\n __typename\n }\n __typename\n }\n __typename\n }\n tokens {\n token\n dataType\n typeEnum\n guid\n tokenMetadata {\n name\n __typename\n }\n __typename\n }\n __typename\n }\n llmReasoning {\n assumptions\n clarifications\n interpretation\n __typename\n }\n tokens\n tmlTokens\n worksheetId\n tokens\n description\n title\n tmlTokens\n cached\n sqlQuery\n sessionId\n genNo\n formulaInfo {\n name\n expression\n __typename\n }\n tmlPhrases\n ambiguousPhrases {\n alternativePhrases {\n phraseType\n token {\n token\n dataType\n typeEnum\n guid\n tokenMetadata {\n name\n __typename\n }\n __typename\n }\n __typename\n }\n ambiguityType\n token {\n token\n dataType\n typeEnum\n guid\n tokenMetadata {\n name\n __typename\n }\n __typename\n }\n __typename\n }\n ambiguousTokens {\n alternativeTokens {\n token\n dataType\n typeEnum\n guid\n tokenMetadata {\n name\n deprecatedTableGuid\n deprecatedTableName\n isFormula\n rootTables {\n created\n description\n guid\n indexVersion\n modified\n name\n __typename\n }\n schemaTableUserDefinedName\n table {\n created\n description\n guid\n indexVersion\n modified\n name\n __typename\n }\n __typename\n }\n __typename\n }\n ambiguityType\n token {\n token\n dataType\n typeEnum\n guid\n tokenMetadata {\n name\n __typename\n }\n __typename\n }\n __typename\n }\n stateKey {\n transactionId\n generationNumber\n transactionId\n __typename\n }\n subQueries {\n tokens\n cohortConfig {\n anchorColumnId\n cohortAnswerGuid\n cohortGroupingType\n cohortGuid\n cohortType\n combineNonGroupValues\n description\n groupExcludedQueryValues\n hideExcludedQueryValues\n isEditable\n name\n nullOutputValue\n returnColumnId\n __typename\n }\n formulas {\n name\n expression\n __typename\n }\n __typename\n }\n visualizationSuggestion {\n chartType\n displayMode\n axisConfigs {\n category\n color\n hidden\n size\n sort\n x\n y\n __typename\n }\n usersVizIntentApplied\n customChartConfigs {\n dimensions {\n columns\n key\n __typename\n }\n key\n __typename\n }\n customChartGuid\n __typename\n }\n tableData {\n columnDataLite {\n columnId\n columnDataType\n dataValue\n columnName\n __typename\n }\n __typename\n }\n warningType\n cached\n __typename\n }\n debugInfo {\n fewShotExamples {\n chartType\n id\n mappingId\n nlQuery\n nlQueryConcepts\n sageQuery\n scope\n sql\n tml\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n errorData {\n tool\n errCode\n errTxt\n toolErrCode\n __typename\n }\n __typename\n }\n type\n __typename\n }\n prevCtx {\n genNo\n sessionId\n __typename\n }\n __typename\n }\n}\n"; +//# sourceMappingURL=conversation-queries.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/graphql/nlsService/conversation-queries.d.ts.map b/dist/src/utils/graphql/nlsService/conversation-queries.d.ts.map new file mode 100644 index 00000000..282217b0 --- /dev/null +++ b/dist/src/utils/graphql/nlsService/conversation-queries.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"conversation-queries.d.ts","sourceRoot":"","sources":["conversation-queries.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,2aAqB9B,CAAC;AAEF,eAAO,MAAM,WAAW,i/OA8RvB,CAAC"} \ No newline at end of file diff --git a/dist/src/utils/graphql/nlsService/conversation-service.d.ts b/dist/src/utils/graphql/nlsService/conversation-service.d.ts new file mode 100644 index 00000000..1dbebe2e --- /dev/null +++ b/dist/src/utils/graphql/nlsService/conversation-service.d.ts @@ -0,0 +1,12 @@ +export declare class Conversation { + private thoughtSpotHost; + private worksheetId; + private conversationId; + private inProgress; + constructor(thoughtSpotHost: string, worksheetId: string); + init(): Promise; + createConversation(): Promise; + sendMessage(userMessage: string): Promise; + executeQuery(query: string, variables: any): Promise; +} +//# sourceMappingURL=conversation-service.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/graphql/nlsService/conversation-service.d.ts.map b/dist/src/utils/graphql/nlsService/conversation-service.d.ts.map new file mode 100644 index 00000000..8a3cdf98 --- /dev/null +++ b/dist/src/utils/graphql/nlsService/conversation-service.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"conversation-service.d.ts","sourceRoot":"","sources":["conversation-service.ts"],"names":[],"mappings":"AAGA,qBAAa,YAAY;IAKT,OAAO,CAAC,eAAe;IAAU,OAAO,CAAC,WAAW;IAJhE,OAAO,CAAC,cAAc,CAAS;IAE/B,OAAO,CAAC,UAAU,CAA6B;gBAE3B,eAAe,EAAE,MAAM,EAAU,WAAW,EAAE,MAAM;IAIlE,IAAI;IAKV,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC;IAqBrB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IA0C9C,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG;CAQ1D"} \ No newline at end of file diff --git a/dist/src/utils/graphql/nlsService/nls-answer-queries.d.ts b/dist/src/utils/graphql/nlsService/nls-answer-queries.d.ts new file mode 100644 index 00000000..f224ef09 --- /dev/null +++ b/dist/src/utils/graphql/nlsService/nls-answer-queries.d.ts @@ -0,0 +1,2 @@ +export declare const getAnswerSessionFromQuery = "\nquery GetEurekaResults($params: Input_eureka_SearchRequest) {\n queryRequest(request: $params) {\n ...eurekaResults\n __typename\n }\n }\n\n fragment eurekaResults on eureka_SearchResponse {\n facets {\n facetType\n facetValue\n facetValues {\n id\n resultCount\n name\n __typename\n }\n __typename\n }\n requestIdentifiers {\n apiRequestId\n appActivityId\n __typename\n }\n sageQuerySuggestions {\n llmReasoning {\n assumptions\n clarifications\n interpretation\n __typename\n }\n tokens\n tmlTokens\n worksheetId\n description\n title\n tmlTokens\n formulaInfo {\n name\n expression\n __typename\n }\n tmlPhrases\n ambiguousPhrases {\n alternativePhrases {\n phraseType\n token {\n token\n dataType\n typeEnum\n guid\n tokenMetadata {\n name\n __typename\n }\n __typename\n }\n __typename\n }\n ambiguityType\n token {\n token\n dataType\n typeEnum\n guid\n tokenMetadata {\n name\n __typename\n }\n __typename\n }\n __typename\n }\n ambiguousTokens {\n alternativeTokens {\n token\n dataType\n typeEnum\n guid\n tokenMetadata {\n name\n deprecatedTableGuid\n deprecatedTableName\n isFormula\n rootTables {\n created\n description\n guid\n indexVersion\n modified\n name\n __typename\n }\n schemaTableUserDefinedName\n table {\n created\n description\n guid\n indexVersion\n modified\n name\n __typename\n }\n __typename\n }\n __typename\n }\n ambiguityType\n token {\n token\n dataType\n typeEnum\n guid\n tokenMetadata {\n name\n __typename\n }\n __typename\n }\n __typename\n }\n sessionId\n genNo\n stateKey {\n generationNumber\n transactionId\n __typename\n }\n subQueries {\n tokens\n cohortConfig {\n anchorColumnId\n cohortAnswerGuid\n cohortGroupingType\n cohortGuid\n cohortType\n combineNonGroupValues\n description\n groupExcludedQueryValues\n hideExcludedQueryValues\n isEditable\n name\n nullOutputValue\n returnColumnId\n __typename\n }\n formulas {\n name\n expression\n __typename\n }\n __typename\n }\n visualizationSuggestion {\n chartType\n displayMode\n axisConfigs {\n category\n color\n hidden\n size\n sort\n x\n y\n __typename\n }\n usersVizIntentApplied\n customChartConfigs {\n dimensions {\n columns\n key\n __typename\n }\n key\n __typename\n }\n customChartGuid\n __typename\n }\n tableData {\n columnDataLite {\n columnId\n columnDataType\n dataValue\n columnName\n __typename\n }\n __typename\n }\n warningType\n cached\n warningDetails {\n warningType\n __typename\n }\n __typename\n }\n results {\n objectSecurityInfo {\n objectType\n objectId\n objectIdForDeletionCheck\n objectTypeForDeletionCheck\n isD13ySourced\n offset\n __typename\n }\n searchAnswer {\n ...eurekaAnswer\n __typename\n }\n searchPinboardViz {\n answer {\n ...eurekaAnswer\n __typename\n }\n pinboardHeader {\n id\n title\n __typename\n }\n __typename\n }\n searchPinboard {\n header {\n ...header\n __typename\n }\n usageInfo {\n ...usageInfo\n __typename\n }\n answers {\n ...eurekaAnswer\n __typename\n }\n vizCount {\n charts\n metrics\n tables\n __typename\n }\n __typename\n }\n snippetInfo {\n titleSnippet {\n snippetString\n highlights {\n start\n end\n __typename\n }\n __typename\n }\n descriptionSnippet {\n snippetString\n highlights {\n start\n end\n __typename\n }\n __typename\n }\n sageQuerySnippet {\n phrase {\n isCompletePhrase\n numTokens\n phraseType\n startIndex\n __typename\n }\n token {\n token\n dataType\n typeEnum\n __typename\n }\n __typename\n }\n sageQuerySnippetWithHighlights {\n highlights {\n start\n end\n __typename\n }\n phraseType\n phraseValue\n __typename\n }\n __typename\n }\n score\n debugInfo\n resultType\n sageQuery\n __typename\n }\n version\n nextPageOffset\n batchSizeRequired\n isFinalPage\n totalResults\n totalFacetResultCount\n errorCode\n debugInfo {\n fewShotExamples {\n chartType\n formulas {\n name\n expression\n __typename\n }\n id\n mappingId\n nlQuery\n nlQueryConcepts\n sageQuery\n scope\n sql\n tml\n feedbackType\n __typename\n }\n __typename\n }\n __typename\n }\n\n fragment eurekaAnswer on eureka_AnswerResult {\n header {\n ...header\n __typename\n }\n usageInfo {\n ...usageInfo\n __typename\n }\n preferredViz {\n ...visualizationMetadata\n __typename\n }\n worksheetInfo {\n ...worksheetInfo\n __typename\n }\n formatted {\n phrase {\n isCompletePhrase\n numTokens\n phraseType\n startIndex\n __typename\n }\n token {\n token\n typeEnum\n __typename\n }\n __typename\n }\n __typename\n }\n\n fragment header on eureka_Header {\n id\n title\n description\n authorGuid\n authorName\n createdOn\n tagIds\n __typename\n }\n\n fragment usageInfo on eureka_UsageInfo {\n favouriteCount\n viewCount\n __typename\n }\n\n fragment visualizationMetadata on eureka_VisualizationMetadata {\n vizType\n chartType\n vizSnapshotRequestData {\n parentReportbookGuid\n parentType\n version\n vizGuid\n __typename\n }\n __typename\n }\n\n fragment worksheetInfo on eureka_WorksheetInfo {\n id\n name\n __typename\n }"; +//# sourceMappingURL=nls-answer-queries.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/graphql/nlsService/nls-answer-queries.d.ts.map b/dist/src/utils/graphql/nlsService/nls-answer-queries.d.ts.map new file mode 100644 index 00000000..f0d7312f --- /dev/null +++ b/dist/src/utils/graphql/nlsService/nls-answer-queries.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"nls-answer-queries.d.ts","sourceRoot":"","sources":["nls-answer-queries.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,yBAAyB,m6SA8Y5B,CAAC"} \ No newline at end of file diff --git a/dist/src/utils/graphql/nlsService/nls-answer-service.d.ts b/dist/src/utils/graphql/nlsService/nls-answer-service.d.ts new file mode 100644 index 00000000..e0b804cb --- /dev/null +++ b/dist/src/utils/graphql/nlsService/nls-answer-service.d.ts @@ -0,0 +1,17 @@ +import { AnswerService } from '../answerService/answerService'; +/** + * Get answer from natural language query + * @param query string + * @param worksheetId string + * @returns AnswerService and the suggestion response. + * @version SDK: 1.33.1 | ThoughtSpot: 10.3.0.cl + * @example + * ```js + * const { answer } = await getAnswerFromQuery('revenue', 'worksheetId'); + * ``` + */ +export declare const getAnswerFromQuery: (query: string, worksheetId: string) => Promise<{ + answer: AnswerService; + suggestion: any; +}>; +//# sourceMappingURL=nls-answer-service.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/graphql/nlsService/nls-answer-service.d.ts.map b/dist/src/utils/graphql/nlsService/nls-answer-service.d.ts.map new file mode 100644 index 00000000..b043612e --- /dev/null +++ b/dist/src/utils/graphql/nlsService/nls-answer-service.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"nls-answer-service.d.ts","sourceRoot":"","sources":["nls-answer-service.ts"],"names":[],"mappings":"AACA,OAAO,EAAoB,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAIjF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,UACpB,MAAM,eACA,MAAM;YACF,aAAa;gBAAc,GAAG;EA2ClD,CAAC"} \ No newline at end of file diff --git a/dist/src/utils/graphql/nlsService/nls-answer-service.spec.d.ts b/dist/src/utils/graphql/nlsService/nls-answer-service.spec.d.ts new file mode 100644 index 00000000..a975bd6d --- /dev/null +++ b/dist/src/utils/graphql/nlsService/nls-answer-service.spec.d.ts @@ -0,0 +1,2 @@ +import 'jest-fetch-mock'; +//# sourceMappingURL=nls-answer-service.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/graphql/nlsService/nls-answer-service.spec.d.ts.map b/dist/src/utils/graphql/nlsService/nls-answer-service.spec.d.ts.map new file mode 100644 index 00000000..db129441 --- /dev/null +++ b/dist/src/utils/graphql/nlsService/nls-answer-service.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"nls-answer-service.spec.d.ts","sourceRoot":"","sources":["nls-answer-service.spec.ts"],"names":[],"mappings":"AAAA,OAAO,iBAAiB,CAAC"} \ No newline at end of file diff --git a/dist/src/utils/graphql/preview-service.d.ts b/dist/src/utils/graphql/preview-service.d.ts new file mode 100644 index 00000000..f974f556 --- /dev/null +++ b/dist/src/utils/graphql/preview-service.d.ts @@ -0,0 +1,9 @@ +export declare const getPreviewQuery = "\nquery GetEurekaVizSnapshots(\n $vizId: String!, $liveboardId: String!) {\n getEurekaVizSnapshot(\n id: $vizId\n reportBookId: $liveboardId\n reportBookType: \"PINBOARD_ANSWER_BOOK\"\n version: 9999999\n ) {\n id\n vizContent\n snapshotType\n createdMs\n }\n } \n"; +/** + * + * @param thoughtSpotHost + * @param vizId + * @param liveboardId + */ +export declare function getPreview(thoughtSpotHost: string, vizId: string, liveboardId: string): Promise; +//# sourceMappingURL=preview-service.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/graphql/preview-service.d.ts.map b/dist/src/utils/graphql/preview-service.d.ts.map new file mode 100644 index 00000000..bdb4d05d --- /dev/null +++ b/dist/src/utils/graphql/preview-service.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"preview-service.d.ts","sourceRoot":"","sources":["preview-service.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,eAAe,kUAe3B,CAAC;AAEF;;;;;GAKG;AACH,wBAAsB,UAAU,CAC5B,eAAe,EAAE,MAAM,EACvB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GACpB,OAAO,CAAC,GAAG,CAAC,CAMd"} \ No newline at end of file diff --git a/dist/src/utils/graphql/preview-service.spec.d.ts b/dist/src/utils/graphql/preview-service.spec.d.ts new file mode 100644 index 00000000..9f6215b6 --- /dev/null +++ b/dist/src/utils/graphql/preview-service.spec.d.ts @@ -0,0 +1,2 @@ +import 'jest-fetch-mock'; +//# sourceMappingURL=preview-service.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/graphql/preview-service.spec.d.ts.map b/dist/src/utils/graphql/preview-service.spec.d.ts.map new file mode 100644 index 00000000..f99afb61 --- /dev/null +++ b/dist/src/utils/graphql/preview-service.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"preview-service.spec.d.ts","sourceRoot":"","sources":["preview-service.spec.ts"],"names":[],"mappings":"AAAA,OAAO,iBAAiB,CAAC"} \ No newline at end of file diff --git a/dist/src/utils/graphql/sourceService.d.ts b/dist/src/utils/graphql/sourceService.d.ts new file mode 100644 index 00000000..0cfeeb4e --- /dev/null +++ b/dist/src/utils/graphql/sourceService.d.ts @@ -0,0 +1,8 @@ +export declare const getSourceDetailQuery = "\n query GetSourceDetail($ids: [GUID!]!) {\n getSourceDetailById(ids: $ids, type: LOGICAL_TABLE) {\n id\n name\n description\n authorName\n authorDisplayName\n isExternal\n type\n created\n modified\n columns {\n id\n name\n author\n authorDisplayName\n description\n dataType\n type\n modified\n ownerName\n owner\n dataRecency\n sources {\n tableId\n tableName\n columnId\n columnName\n __typename\n }\n synonyms\n cohortAnswerId\n __typename\n }\n relationships\n destinationRelationships\n dataSourceId\n __typename\n }\n } \n"; +/** + * + * @param thoughtSpotHost + * @param sourceId + */ +export declare function getSourceDetail(thoughtSpotHost: string, sourceId: string): Promise; +//# sourceMappingURL=sourceService.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/graphql/sourceService.d.ts.map b/dist/src/utils/graphql/sourceService.d.ts.map new file mode 100644 index 00000000..1b7ceffc --- /dev/null +++ b/dist/src/utils/graphql/sourceService.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"sourceService.d.ts","sourceRoot":"","sources":["sourceService.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,oBAAoB,43BAyChC,CAAC;AAIF;;;;GAIG;AACH,wBAAsB,eAAe,CACjC,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,MAAM,GACjB,OAAO,CAAC,GAAG,CAAC,CAkBd"} \ No newline at end of file diff --git a/dist/src/utils/graphql/sourceService.spec.d.ts b/dist/src/utils/graphql/sourceService.spec.d.ts new file mode 100644 index 00000000..caba6544 --- /dev/null +++ b/dist/src/utils/graphql/sourceService.spec.d.ts @@ -0,0 +1,2 @@ +import 'jest-fetch-mock'; +//# sourceMappingURL=sourceService.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/graphql/sourceService.spec.d.ts.map b/dist/src/utils/graphql/sourceService.spec.d.ts.map new file mode 100644 index 00000000..2adf8fce --- /dev/null +++ b/dist/src/utils/graphql/sourceService.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"sourceService.spec.d.ts","sourceRoot":"","sources":["sourceService.spec.ts"],"names":[],"mappings":"AAAA,OAAO,iBAAiB,CAAC"} \ No newline at end of file diff --git a/dist/src/utils/liveboardService/liveboardService.d.ts b/dist/src/utils/liveboardService/liveboardService.d.ts new file mode 100644 index 00000000..e66e7b34 --- /dev/null +++ b/dist/src/utils/liveboardService/liveboardService.d.ts @@ -0,0 +1,31 @@ +import { AnswerService } from '../graphql/answerService/answerService'; +/** + * Creates a new Liveboard in ThoughtSpot using the provided AnswerService instances. + * + * Each answer will be added as a visualization to the newly created Liveboard. + * + * @param {AnswerService[]} answers - An array of initialized `AnswerService` instances + * representing the answers to be added to the Liveboard. + * @param {string} name - The name of the Liveboard to create. + * @returns result Promise + * @version SDK: 1.33.1 | ThoughtSpot: * + * @example + * ```js + * import { EmbedEvent, AnswerService } from "@thoughtspot/visual-embed-sdk"; + * + * embed.on(EmbedEvent.Data, async () => { + * try { + * const answerService = await embed.getAnswerService(); + * const lb = await createLiveboardWithAnswers( + * [answerService], + * "My Liveboard" + * ); + * console.log("Liveboard created:", lb); + * } catch (err) { + * console.error("Failed to create liveboard:", err); + * } + * }); + * ``` + */ +export declare const createLiveboardWithAnswers: (answers: AnswerService[], name: string) => Promise; +//# sourceMappingURL=liveboardService.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/liveboardService/liveboardService.d.ts.map b/dist/src/utils/liveboardService/liveboardService.d.ts.map new file mode 100644 index 00000000..49b5c23c --- /dev/null +++ b/dist/src/utils/liveboardService/liveboardService.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"liveboardService.d.ts","sourceRoot":"","sources":["liveboardService.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAC;AAIvE;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,0BAA0B,YAC1B,aAAa,EAAE,QAClB,MAAM,KACb,QAAQ,GAAG,CA8Cb,CAAC"} \ No newline at end of file diff --git a/dist/src/utils/liveboardService/liveboardService.spec.d.ts b/dist/src/utils/liveboardService/liveboardService.spec.d.ts new file mode 100644 index 00000000..7d17b67b --- /dev/null +++ b/dist/src/utils/liveboardService/liveboardService.spec.d.ts @@ -0,0 +1,2 @@ +import 'jest-fetch-mock'; +//# sourceMappingURL=liveboardService.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/liveboardService/liveboardService.spec.d.ts.map b/dist/src/utils/liveboardService/liveboardService.spec.d.ts.map new file mode 100644 index 00000000..9ce7fc1c --- /dev/null +++ b/dist/src/utils/liveboardService/liveboardService.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"liveboardService.spec.d.ts","sourceRoot":"","sources":["liveboardService.spec.ts"],"names":[],"mappings":"AAAA,OAAO,iBAAiB,CAAC"} \ No newline at end of file diff --git a/dist/src/utils/logger.d.ts b/dist/src/utils/logger.d.ts new file mode 100644 index 00000000..014a8ed3 --- /dev/null +++ b/dist/src/utils/logger.d.ts @@ -0,0 +1,18 @@ +import { LogLevel } from '../types'; +declare const setGlobalLogLevelOverride: (logLevel: LogLevel) => void; +declare class Logger { + private logLevel; + setLogLevel: (newLogLevel: LogLevel) => void; + getLogLevel: () => LogLevel; + canLog(logLevel: LogLevel): boolean; + logMessages(args: any[], logLevel: LogLevel): void; + log(...args: any[]): void; + info(...args: any[]): void; + debug(...args: any[]): void; + trace(...args: any[]): void; + error(...args: any[]): void; + warn(...args: any[]): void; +} +declare const logger: Logger; +export { LogLevel, logger, setGlobalLogLevelOverride }; +//# sourceMappingURL=logger.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/logger.d.ts.map b/dist/src/utils/logger.d.ts.map new file mode 100644 index 00000000..d3dc9363 --- /dev/null +++ b/dist/src/utils/logger.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAcpC,QAAA,MAAM,yBAAyB,aAAc,QAAQ,KAAG,IAEvD,CAAC;AAgBF,cAAM,MAAM;IACR,OAAO,CAAC,QAAQ,CAA4B;IAErC,WAAW,gBAAiB,QAAQ,KAAG,IAAI,CAEhD;IAEK,WAAW,QAAO,QAAQ,CAAkB;IAE5C,MAAM,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO;IAQnC,WAAW,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI;IASlD,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI;IAIzB,IAAI,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI;IAI1B,KAAK,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI;IAI3B,KAAK,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI;IAI3B,KAAK,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI;IAI3B,IAAI,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI;CAGpC;AAED,QAAA,MAAM,MAAM,QAAe,CAAC;AAE5B,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,yBAAyB,EAAE,CAAC"} \ No newline at end of file diff --git a/dist/src/utils/logger.spec.d.ts b/dist/src/utils/logger.spec.d.ts new file mode 100644 index 00000000..3ae4a6ea --- /dev/null +++ b/dist/src/utils/logger.spec.d.ts @@ -0,0 +1,11 @@ +/// +declare let logger: any; +declare let LogLevel: any; +declare let setGlobalLogLevelOverride: any; +declare const consoleErrorSpy: jest.SpyInstance; +declare const consoleWarnSpy: jest.SpyInstance; +declare const consoleInfoSpy: jest.SpyInstance; +declare const consoleDebugSpy: jest.SpyInstance; +declare const consoleTraceSpy: jest.SpyInstance; +declare const versionPrefix: any; +//# sourceMappingURL=logger.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/logger.spec.d.ts.map b/dist/src/utils/logger.spec.d.ts.map new file mode 100644 index 00000000..73a9ed88 --- /dev/null +++ b/dist/src/utils/logger.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"logger.spec.d.ts","sourceRoot":"","sources":["logger.spec.ts"],"names":[],"mappings":";AAAA,QAAA,IAAI,MAAM,EAAE,GAAG,CAAC;AAChB,QAAA,IAAI,QAAQ,EAAE,GAAG,CAAC;AAClB,QAAA,IAAI,yBAAyB,EAAE,GAAG,CAAC;AAEnC,QAAA,MAAM,eAAe,wEAA+B,CAAC;AACrD,QAAA,MAAM,cAAc,wEAA8B,CAAC;AACnD,QAAA,MAAM,cAAc,wEAA8B,CAAC;AACnD,QAAA,MAAM,eAAe,wEAA+B,CAAC;AACrD,QAAA,MAAM,eAAe,wEAA+B,CAAC;AAErD,QAAA,MAAM,aAAa,KAAqC,CAAC"} \ No newline at end of file diff --git a/dist/src/utils/processData.d.ts b/dist/src/utils/processData.d.ts new file mode 100644 index 00000000..5122f68c --- /dev/null +++ b/dist/src/utils/processData.d.ts @@ -0,0 +1,22 @@ +import { EmbedEvent } from '../types'; +/** + * + * @param e + * @param thoughtSpotHost + */ +export declare function processCustomAction(e: any, thoughtSpotHost: string): any; +/** + * + * @param e + * @param containerEl + */ +export declare function processAuthFailure(e: any, containerEl: Element): any; +/** + * + * @param type + * @param e + * @param thoughtSpotHost + * @param containerEl + */ +export declare function processEventData(type: EmbedEvent, eventData: any, thoughtSpotHost: string, containerEl: Element): any; +//# sourceMappingURL=processData.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/processData.d.ts.map b/dist/src/utils/processData.d.ts.map new file mode 100644 index 00000000..d2b2586c --- /dev/null +++ b/dist/src/utils/processData.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"processData.d.ts","sourceRoot":"","sources":["processData.ts"],"names":[],"mappings":"AAQA,OAAO,EAAiC,UAAU,EAAE,MAAM,UAAU,CAAC;AA2BrE;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,GAAG,EAAE,eAAe,EAAE,MAAM,OAYlE;AAyCD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,GAAG,EAAE,WAAW,EAAE,OAAO,OAiB9D;AAgBD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC5B,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,GAAG,EACd,eAAe,EAAE,MAAM,EACvB,WAAW,EAAE,OAAO,GACrB,GAAG,CAmBL"} \ No newline at end of file diff --git a/dist/src/utils/processData.spec.d.ts b/dist/src/utils/processData.spec.d.ts new file mode 100644 index 00000000..ad7e11d8 --- /dev/null +++ b/dist/src/utils/processData.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=processData.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/processData.spec.d.ts.map b/dist/src/utils/processData.spec.d.ts.map new file mode 100644 index 00000000..cdf50806 --- /dev/null +++ b/dist/src/utils/processData.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"processData.spec.d.ts","sourceRoot":"","sources":["processData.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/utils/processTrigger.d.ts b/dist/src/utils/processTrigger.d.ts new file mode 100644 index 00000000..73ce4406 --- /dev/null +++ b/dist/src/utils/processTrigger.d.ts @@ -0,0 +1,17 @@ +import { ContextType, HostEvent } from '../types'; +/** + * Reloads the ThoughtSpot iframe. + * @param iFrame + */ +export declare const reload: (iFrame: HTMLIFrameElement) => void; +export declare const TRIGGER_TIMEOUT = 30000; +/** + * + * @param iFrame + * @param messageType + * @param thoughtSpotHost + * @param data + * @param context + */ +export declare function processTrigger(iFrame: HTMLIFrameElement, messageType: HostEvent, thoughtSpotHost: string, data: any, context?: ContextType): Promise; +//# sourceMappingURL=processTrigger.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/processTrigger.d.ts.map b/dist/src/utils/processTrigger.d.ts.map new file mode 100644 index 00000000..b00cdd23 --- /dev/null +++ b/dist/src/utils/processTrigger.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"processTrigger.d.ts","sourceRoot":"","sources":["processTrigger.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAkB,MAAM,UAAU,CAAC;AAKlE;;;GAGG;AACH,eAAO,MAAM,MAAM,WAAY,iBAAiB,SAM/C,CAAC;AAqBF,eAAO,MAAM,eAAe,QAAQ,CAAC;AAErC;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC1B,MAAM,EAAE,iBAAiB,EACzB,WAAW,EAAE,SAAS,EACtB,eAAe,EAAE,MAAM,EACvB,IAAI,EAAE,GAAG,EACT,OAAO,CAAC,EAAE,WAAW,GACtB,OAAO,CAAC,GAAG,CAAC,CAqCd"} \ No newline at end of file diff --git a/dist/src/utils/processTrigger.spec.d.ts b/dist/src/utils/processTrigger.spec.d.ts new file mode 100644 index 00000000..bfd4a8d5 --- /dev/null +++ b/dist/src/utils/processTrigger.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=processTrigger.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/processTrigger.spec.d.ts.map b/dist/src/utils/processTrigger.spec.d.ts.map new file mode 100644 index 00000000..9750ce5d --- /dev/null +++ b/dist/src/utils/processTrigger.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"processTrigger.spec.d.ts","sourceRoot":"","sources":["processTrigger.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/utils/reporting.d.ts b/dist/src/utils/reporting.d.ts new file mode 100644 index 00000000..95ae5bbd --- /dev/null +++ b/dist/src/utils/reporting.d.ts @@ -0,0 +1,16 @@ +/** + * Register a global ReportingObserver to capture all unhandled errors + * @param overrideExisting boolean to override existing observer + * @returns ReportingObserver | null + */ +export declare function registerReportingObserver(overrideExisting?: boolean): ReportingObserver | null; +/** + * Get the global ReportingObserver + * @returns - ReportingObserver | null + */ +export declare function getGlobalReportingObserver(): ReportingObserver | null; +/** + * Resets the global ReportingObserver + */ +export declare function resetGlobalReportingObserver(): void; +//# sourceMappingURL=reporting.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/reporting.d.ts.map b/dist/src/utils/reporting.d.ts.map new file mode 100644 index 00000000..2d984ef2 --- /dev/null +++ b/dist/src/utils/reporting.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"reporting.d.ts","sourceRoot":"","sources":["reporting.ts"],"names":[],"mappings":"AAYA;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,gBAAgB,UAAQ,GAAG,iBAAiB,GAAG,IAAI,CAqC5F;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,IAAI,iBAAiB,GAAG,IAAI,CAErE;AAED;;GAEG;AACH,wBAAgB,4BAA4B,IAAI,IAAI,CAGnD"} \ No newline at end of file diff --git a/dist/src/utils/reporting.spec.d.ts b/dist/src/utils/reporting.spec.d.ts new file mode 100644 index 00000000..3884fb67 --- /dev/null +++ b/dist/src/utils/reporting.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=reporting.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/reporting.spec.d.ts.map b/dist/src/utils/reporting.spec.d.ts.map new file mode 100644 index 00000000..cbdb83e7 --- /dev/null +++ b/dist/src/utils/reporting.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"reporting.spec.d.ts","sourceRoot":"","sources":["reporting.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/utils/resetServices.d.ts b/dist/src/utils/resetServices.d.ts new file mode 100644 index 00000000..349f22bd --- /dev/null +++ b/dist/src/utils/resetServices.d.ts @@ -0,0 +1,8 @@ +/** + * This function resets all the services that are cached in the SDK. + * This is to be called when the user logs out of the application and also + * when init is called again. + * @version SDK: 1.30.2 | ThoughtSpot: * + */ +export declare function resetAllCachedServices(): void; +//# sourceMappingURL=resetServices.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/resetServices.d.ts.map b/dist/src/utils/resetServices.d.ts.map new file mode 100644 index 00000000..adf6496e --- /dev/null +++ b/dist/src/utils/resetServices.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"resetServices.d.ts","sourceRoot":"","sources":["resetServices.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAI7C"} \ No newline at end of file diff --git a/dist/src/utils/resetServices.spec.d.ts b/dist/src/utils/resetServices.spec.d.ts new file mode 100644 index 00000000..2c2fb4e6 --- /dev/null +++ b/dist/src/utils/resetServices.spec.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=resetServices.spec.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/resetServices.spec.d.ts.map b/dist/src/utils/resetServices.spec.d.ts.map new file mode 100644 index 00000000..b23a7ddf --- /dev/null +++ b/dist/src/utils/resetServices.spec.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"resetServices.spec.d.ts","sourceRoot":"","sources":["resetServices.spec.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/src/utils/sessionInfoService.d.ts b/dist/src/utils/sessionInfoService.d.ts new file mode 100644 index 00000000..01d86c42 --- /dev/null +++ b/dist/src/utils/sessionInfoService.d.ts @@ -0,0 +1,104 @@ +export type SessionInfo = { + releaseVersion: string; + userGUID: string; + currentOrgId: number; + privileges: string[]; + mixpanelToken: string; + isPublicUser: boolean; + clusterId: string; + clusterName: string; + [key: string]: any; +}; +export type PreauthInfo = { + info?: SessionInfo; + headers: Record; + status: number; + [key: string]: any; +}; +/** + * Processes the session info response and returns the session info object. + * @param preauthInfoResp {any} Response from the session info API. + * @returns {PreauthInfo} The session info object. + * @example ```js + * const preauthInfoResp = await fetch(sessionInfoPath); + * const sessionInfo = await formatPreauthInfo(preauthInfoResp); + * console.log(sessionInfo); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + */ +export declare const formatPreauthInfo: (preauthInfoResp: any) => Promise; +/** + * Returns the session info object and caches it for future use. + * Once fetched the session info object is cached and returned from the cache on + * subsequent calls. + * @example ```js + * const preauthInfo = await getPreauthInfo(); + * console.log(preauthInfo); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + * @returns {Promise} The session info object. + */ +export declare function getPreauthInfo(allowCache?: boolean): Promise; +/** + * Returns the cached session info object and caches it for future use. + * Once fetched the session info object is cached and returned from the cache on + * subsequent calls. + * This cache is cleared when inti is called OR resetCachedSessionInfo is called. + * @example ```js + * const sessionInfo = await getSessionInfo(); + * console.log(sessionInfo); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + * @returns {Promise} The session info object. + */ +export declare function getSessionInfo(): Promise; +/** + * Returns the cached session info object. If the client is not authenticated the + * function will return null. + * @example ```js + * const sessionInfo = getCachedSessionInfo(); + * if (sessionInfo) { + * console.log(sessionInfo); + * } else { + * console.log('Not authenticated'); + * } + * ``` + * @returns {SessionInfo | null} The session info object. + * @version SDK: 1.28.3 | ThoughtSpot: * + */ +export declare function getCachedSessionInfo(): SessionInfo | null; +/** + * Processes the session info response and returns the session info object. + * @param sessionInfoResp {any} Response from the session info API. + * @returns {SessionInfo} The session info object. + * @example ```js + * const sessionInfoResp = await fetch(sessionInfoPath); + * const sessionInfo = getSessionDetails(sessionInfoResp); + * console.log(sessionInfo); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + */ +export declare const getSessionDetails: (sessionInfoResp: any) => SessionInfo; +/** + * Resets the cached session info object and forces a new fetch on the next call. + * @example ```js + * resetCachedSessionInfo(); + * const sessionInfo = await getSessionInfo(); + * console.log(sessionInfo); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + * @returns {void} + */ +export declare function resetCachedSessionInfo(): void; +/** + * Resets the cached preauth info object and forces a new fetch on the next call. + * @example ```js + * resetCachedPreauthInfo(); + * const preauthInfo = await getPreauthInfo(); + * console.log(preauthInfo); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + * @returns {void} + */ +export declare function resetCachedPreauthInfo(): void; +//# sourceMappingURL=sessionInfoService.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/sessionInfoService.d.ts.map b/dist/src/utils/sessionInfoService.d.ts.map new file mode 100644 index 00000000..0742dec9 --- /dev/null +++ b/dist/src/utils/sessionInfoService.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"sessionInfoService.d.ts","sourceRoot":"","sources":["sessionInfoService.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,WAAW,GAAG;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACtB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACtB,CAAC;AAKF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,oBAA2B,GAAG,KAAG,QAAQ,WAAW,CAgBjF,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,CAAC,UAAU,UAAO,GAAG,OAAO,CAAC,WAAW,CAAC,CAa5E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,WAAW,CAAC,CAQ3D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,IAAI,WAAW,GAAG,IAAI,CAEzD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,oBAAqB,GAAG,KAAG,WAexD,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C"} \ No newline at end of file diff --git a/dist/src/utils/with-resolvers-polyfill.d.ts b/dist/src/utils/with-resolvers-polyfill.d.ts new file mode 100644 index 00000000..cfe1d55a --- /dev/null +++ b/dist/src/utils/with-resolvers-polyfill.d.ts @@ -0,0 +1 @@ +//# sourceMappingURL=with-resolvers-polyfill.d.ts.map \ No newline at end of file diff --git a/dist/src/utils/with-resolvers-polyfill.d.ts.map b/dist/src/utils/with-resolvers-polyfill.d.ts.map new file mode 100644 index 00000000..48474c5c --- /dev/null +++ b/dist/src/utils/with-resolvers-polyfill.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"with-resolvers-polyfill.d.ts","sourceRoot":"","sources":["with-resolvers-polyfill.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/tsembed-react.es.js b/dist/tsembed-react.es.js new file mode 100644 index 00000000..8352c5ac --- /dev/null +++ b/dist/tsembed-react.es.js @@ -0,0 +1,24115 @@ +/* @thoughtspot/visual-embed-sdk version 1.48.0 */ +'use client'; +import * as React from 'react'; +import React__default, { useRef, useCallback } from 'react'; + +function _mergeNamespaces(n, m) { + m.forEach(function (e) { + e && typeof e !== 'string' && !Array.isArray(e) && Object.keys(e).forEach(function (k) { + if (k !== 'default' && !(k in n)) { + var d = Object.getOwnPropertyDescriptor(e, k); + Object.defineProperty(n, k, d.get ? d : { + enumerable: true, + get: function () { return e[k]; } + }); + } + }); + }); + return Object.freeze(n); +} + +var has = Object.prototype.hasOwnProperty; + +function find(iter, tar, key) { + for (key of iter.keys()) { + if (dequal(key, tar)) return key; + } +} + +function dequal(foo, bar) { + var ctor, len, tmp; + if (foo === bar) return true; + + if (foo && bar && (ctor=foo.constructor) === bar.constructor) { + if (ctor === Date) return foo.getTime() === bar.getTime(); + if (ctor === RegExp) return foo.toString() === bar.toString(); + + if (ctor === Array) { + if ((len=foo.length) === bar.length) { + while (len-- && dequal(foo[len], bar[len])); + } + return len === -1; + } + + if (ctor === Set) { + if (foo.size !== bar.size) { + return false; + } + for (len of foo) { + tmp = len; + if (tmp && typeof tmp === 'object') { + tmp = find(bar, tmp); + if (!tmp) return false; + } + if (!bar.has(tmp)) return false; + } + return true; + } + + if (ctor === Map) { + if (foo.size !== bar.size) { + return false; + } + for (len of foo) { + tmp = len[0]; + if (tmp && typeof tmp === 'object') { + tmp = find(bar, tmp); + if (!tmp) return false; + } + if (!dequal(len[1], bar.get(tmp))) { + return false; + } + } + return true; + } + + if (ctor === ArrayBuffer) { + foo = new Uint8Array(foo); + bar = new Uint8Array(bar); + } else if (ctor === DataView) { + if ((len=foo.byteLength) === bar.byteLength) { + while (len-- && foo.getInt8(len) === bar.getInt8(len)); + } + return len === -1; + } + + if (ArrayBuffer.isView(foo)) { + if ((len=foo.byteLength) === bar.byteLength) { + while (len-- && foo[len] === bar[len]); + } + return len === -1; + } + + if (!ctor || typeof foo === 'object') { + len = 0; + for (ctor in foo) { + if (has.call(foo, ctor) && ++len && !has.call(bar, ctor)) return false; + if (!(ctor in bar) || !dequal(foo[ctor], bar[ctor])) return false; + } + return Object.keys(bar).length === len; + } + } + + return foo !== foo && bar !== bar; +} + +/** + * @param value the value to be memoized (usually a dependency list) + * @returns a momoized version of the value as long as it remains deeply equal + */ + + +function useDeepCompareMemoize(value) { + var ref = React.useRef(value); + var signalRef = React.useRef(0); + + if (!dequal(value, ref.current)) { + ref.current = value; + signalRef.current += 1; + } // eslint-disable-next-line react-hooks/exhaustive-deps + + + return React.useMemo(function () { + return ref.current; + }, [signalRef.current]); +} + +function useDeepCompareEffect(callback, dependencies) { + + + return React.useEffect(callback, useDeepCompareMemoize(dependencies)); +} + +// istanbul ignore next +const isObject$1 = (obj) => { + if (typeof obj === "object" && obj !== null) { + if (typeof Object.getPrototypeOf === "function") { + const prototype = Object.getPrototypeOf(obj); + return prototype === Object.prototype || prototype === null; + } + return Object.prototype.toString.call(obj) === "[object Object]"; + } + return false; +}; +const merge = (...objects) => objects.reduce((result, current) => { + if (Array.isArray(current)) { + throw new TypeError("Arguments provided to ts-deepmerge must be objects, not arrays."); + } + Object.keys(current).forEach((key) => { + if (["__proto__", "constructor", "prototype"].includes(key)) { + return; + } + if (Array.isArray(result[key]) && Array.isArray(current[key])) { + result[key] = merge.options.mergeArrays + ? merge.options.uniqueArrayItems + ? Array.from(new Set(result[key].concat(current[key]))) + : [...result[key], ...current[key]] + : current[key]; + } + else if (isObject$1(result[key]) && isObject$1(current[key])) { + result[key] = merge(result[key], current[key]); + } + else { + result[key] = + current[key] === undefined + ? merge.options.allowUndefinedOverrides + ? current[key] + : result[key] + : current[key]; + } + }); + return result; +}, {}); +const defaultOptions = { + allowUndefinedOverrides: true, + mergeArrays: true, + uniqueArrayItems: true, +}; +merge.options = defaultOptions; +merge.withOptions = (options, ...objects) => { + merge.options = Object.assign(Object.assign({}, defaultOptions), options); + const result = merge(...objects); + merge.options = defaultOptions; + return result; +}; + +/** + * Copyright (c) 2023 + * + * TypeScript type definitions for ThoughtSpot Visual Embed SDK + * @summary Type definitions for Embed SDK + * @author Ayon Ghosh + */ +/** + * The authentication mechanism for allowing access to + * the embedded app + * @group Authentication / Init + */ +var AuthType; +(function (AuthType) { + /** + * No authentication on the SDK. Pass-through to the embedded App. Alias for + * `Passthrough`. + * @example + * ```js + * init({ + * // ... + * authType: AuthType.None, + * }); + * ``` + */ + AuthType["None"] = "None"; + /** + * Passthrough SSO to the embedded application within the iframe. Requires least + * configuration, but may not be supported by all IDPs. This will behave like `None` + * if SSO is not configured on ThoughtSpot. + * + * To use this: + * Your SAML or OpenID provider must allow iframe redirects. + * For example, if you are using Okta as IdP, you can enable iframe embedding. + * @version SDK: 1.15.0 | ThoughtSpot: 8.8.0.cl + * @example + * ```js + * init({ + * // ... + * authType: AuthType.EmbeddedSSO, + * }); + * ``` + */ + AuthType["EmbeddedSSO"] = "EmbeddedSSO"; + /** + * SSO using SAML, Use {@link SAMLRedirect} instead + * @deprecated This option is deprecated. + * @hidden + */ + AuthType["SSO"] = "SSO_SAML"; + /** + * SSO using SAML, Use {@link SAMLRedirect} instead + * @deprecated This option is deprecated. + * @hidden + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + AuthType["SAML"] = "SSO_SAML"; + /** + * SSO using SAML + * Makes the host application redirect to the SAML IdP. Use this + * if your IdP does not allow itself to be embedded. + * + * This redirects the host application to the SAML IdP. The host application + * will be redirected back to the ThoughtSpot app after authentication. + * @example + * ```js + * init({ + * // ... + * authType: AuthType.SAMLRedirect, + * }); + * ``` + * + * This opens the SAML IdP in a popup window. The popup is triggered + * when the user clicks the trigger button. The popup window will be + * closed automatically after authentication. + * @example + * ```js + * init({ + * // ... + * authType: AuthType.SAMLRedirect, + * authTriggerText: 'Login with SAML', + * authTriggerContainer: '#tsEmbed', + * inPopup: true, + * }); + * ``` + * + * Can also use the event to trigger the popup flow. Works the same + * as the above example. + * @example + * ```js + * const authEE = init({ + * // ... + * authType: AuthType.SAMLRedirect, + * inPopup: true, + * }); + * + * someButtonOnYourPage.addEventListener('click', () => { + * authEE.emit(AuthEvent.TRIGGER_SSO_POPUP); + * }); + * ``` + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + AuthType["SAMLRedirect"] = "SSO_SAML"; + /** + * SSO using OIDC + * SSO using OIDC, Use {@link OIDCRedirect} instead + * @deprecated This option is deprecated. + * @hidden + */ + AuthType["OIDC"] = "SSO_OIDC"; + /** + * SSO using OIDC + * Will make the host application redirect to the OIDC IdP. + * See code samples in {@link SAMLRedirect}. + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + AuthType["OIDCRedirect"] = "SSO_OIDC"; + /** + * Trusted authentication server + * Use {@link TrustedAuth} instead + * @deprecated This option is deprecated. + * @hidden + */ + AuthType["AuthServer"] = "AuthServer"; + /** + * Trusted authentication server. Use your own authentication server + * which returns a bearer token, generated using the `secret_key` obtained + * from ThoughtSpot. + * @example + * ```js + * init({ + * // ... + * authType: AuthType.TrustedAuthToken, + * getAuthToken: () => { + * return fetch('https://my-backend.app/ts-token') + * .then((response) => response.json()) + * .then((data) => data.token); + * } + * }); + * ``` + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + AuthType["TrustedAuthToken"] = "AuthServer"; + /** + * Trusted authentication server Cookieless, Use your own authentication + * server which returns a bearer token, generated using the `secret_key` + * obtained from ThoughtSpot. This uses a cookieless authentication + * approach, recommended to bypass the third-party cookie-blocking restriction + * implemented by some browsers. + * @version SDK: 1.22.0 | ThoughtSpot: 9.3.0.cl, 9.5.1.sw + * @example + * ```js + * init({ + * // ... + * authType: AuthType.TrustedAuthTokenCookieless, + * getAuthToken: () => { + * return fetch('https://my-backend.app/ts-token') + * .then((response) => response.json()) + * .then((data) => data.token); + * } + * }); + * ``` + */ + AuthType["TrustedAuthTokenCookieless"] = "AuthServerCookieless"; + /** + * Use the ThoughtSpot login API to authenticate to the cluster directly. + * + * Warning: This feature is primarily intended for developer testing. It is + * strongly advised not to use this authentication method in production. + */ + AuthType["Basic"] = "Basic"; +})(AuthType || (AuthType = {})); +/** + * + * **Note**: This attribute is not supported in the classic (V1) homepage experience. + * + */ +var HomeLeftNavItem; +(function (HomeLeftNavItem) { + /** + * The *Search data* option in + * the *Insights* left navigation panel. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + */ + HomeLeftNavItem["SearchData"] = "search-data"; + /** + * The *Home* menu option in + * the *Insights* left navigation panel. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + */ + HomeLeftNavItem["Home"] = "insights-home"; + /** + * The *Liveboards* menu option in + * the *Insights* left navigation panel. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + */ + HomeLeftNavItem["Liveboards"] = "liveboards"; + /** + * The *Answers* menu option in + * the *Insights* left navigation panel. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + */ + HomeLeftNavItem["Answers"] = "answers"; + /** + * The *Monitor subscriptions* menu option in + * the *Insights* left navigation panel. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + */ + HomeLeftNavItem["MonitorSubscription"] = "monitor-alerts"; + /** + * The *SpotIQ analysis* menu option in + * the *Insights* left navigation panel. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + */ + HomeLeftNavItem["SpotIQAnalysis"] = "spotiq-analysis"; + /** + * The *Liveboard schedules* menu option in + * the *Insights* left navigation panel. + * @version SDK: 1.34.0 | ThoughtSpot: 10.3.0.cl + */ + HomeLeftNavItem["LiveboardSchedules"] = "liveboard-schedules"; + /** + * The create option in the *Insights* + * left navigation panel. + * Available in the V3 navigation experience. + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HomeLeftNavItem["Create"] = "create"; + /** + * The *Spotter* menu option in the *Insights* + * left navigation panel. + * Available in the V3 navigation experience. + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HomeLeftNavItem["Spotter"] = "spotter"; + /** + * The *Favorites* section in the *Insights* + * left navigation panel. + * Available in the V3 navigation experience. + * @version SDK: 1.41.0 | ThoughtSpot: 10.12.0.cl + */ + HomeLeftNavItem["Favorites"] = "favorites"; +})(HomeLeftNavItem || (HomeLeftNavItem = {})); +/** + * A map of the supported runtime filter operations + */ +var RuntimeFilterOp; +(function (RuntimeFilterOp) { + /** + * Equals + */ + RuntimeFilterOp["EQ"] = "EQ"; + /** + * Does not equal + */ + RuntimeFilterOp["NE"] = "NE"; + /** + * Less than + */ + RuntimeFilterOp["LT"] = "LT"; + /** + * Less than or equal to + */ + RuntimeFilterOp["LE"] = "LE"; + /** + * Greater than + */ + RuntimeFilterOp["GT"] = "GT"; + /** + * Greater than or equal to + */ + RuntimeFilterOp["GE"] = "GE"; + /** + * Contains + */ + RuntimeFilterOp["CONTAINS"] = "CONTAINS"; + /** + * Begins with + */ + RuntimeFilterOp["BEGINS_WITH"] = "BEGINS_WITH"; + /** + * Ends with + */ + RuntimeFilterOp["ENDS_WITH"] = "ENDS_WITH"; + /** + * Between, inclusive of higher value + */ + RuntimeFilterOp["BW_INC_MAX"] = "BW_INC_MAX"; + /** + * Between, inclusive of lower value + */ + RuntimeFilterOp["BW_INC_MIN"] = "BW_INC_MIN"; + /** + * Between, inclusive of both higher and lower value + */ + RuntimeFilterOp["BW_INC"] = "BW_INC"; + /** + * Between, non-inclusive + */ + RuntimeFilterOp["BW"] = "BW"; + /** + * Is included in this list of values + */ + RuntimeFilterOp["IN"] = "IN"; + /** + * Is not included in this list of values + */ + RuntimeFilterOp["NOT_IN"] = "NOT_IN"; +})(RuntimeFilterOp || (RuntimeFilterOp = {})); +/** + * Home page modules that can be hidden + * via `hiddenHomepageModules` and reordered via + * `reorderedHomepageModules`. + * + * **Note**: This option is not supported in the classic (v1) experience. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl, 10.1.0.sw + */ +var HomepageModule; +(function (HomepageModule) { + /** + * Search bar + */ + HomepageModule["Search"] = "SEARCH"; + /** + * KPI watchlist module + */ + HomepageModule["Watchlist"] = "WATCHLIST"; + /** + * Favorite module + */ + HomepageModule["Favorite"] = "FAVORITE"; + /** + * List of answers and Liveboards + */ + HomepageModule["MyLibrary"] = "MY_LIBRARY"; + /** + * Trending list + */ + HomepageModule["Trending"] = "TRENDING"; + /** + * Learning videos + */ + HomepageModule["Learning"] = "LEARNING"; +})(HomepageModule || (HomepageModule = {})); +/** + * List page columns that can be hidden. + * **Note**: This option is applicable to full app embedding only. + * @version SDK: 1.38.0 | ThoughtSpot: 10.9.0.cl + */ +var ListPageColumns; +(function (ListPageColumns) { + /** + * Favorites + */ + ListPageColumns["Favorites"] = "FAVOURITE"; + /** + * Favourite Use {@link ListPageColumns.Favorites} instead. + * @deprecated This option is deprecated. + */ + ListPageColumns["Favourite"] = "FAVOURITE"; + /** + * Tags + */ + ListPageColumns["Tags"] = "TAGS"; + /** + * Author + */ + ListPageColumns["Author"] = "AUTHOR"; + /** + * Last viewed/Last modified + */ + ListPageColumns["DateSort"] = "DATE_SORT"; + /** + * Share + */ + ListPageColumns["Share"] = "SHARE"; + /** + * Verified badge/column + */ + ListPageColumns["Verified"] = "VERIFIED"; +})(ListPageColumns || (ListPageColumns = {})); +/** + * Event types emitted by the embedded ThoughtSpot application. + * + * To add an event listener use the corresponding + * {@link LiveboardEmbed.on} or {@link AppEmbed.on} or {@link SearchEmbed.on} method. + * @example + * ```js + * import { EmbedEvent } from '@thoughtspot/visual-embed-sdk'; + * // Or + * // const { EmbedEvent } = window.tsembed; + * + * // create the liveboard embed. + * + * liveboardEmbed.on(EmbedEvent.Drilldown, (drilldown) => { + * console.log('Drilldown event', drilldown); + * })); + * ``` + * + * If you are using React components for embedding, you can register to any + * events from the `EmbedEvent` list by using the `on` convention. + * For example,`onAlert`, `onCopyToClipboard` and so on. + * @example + * ```js + * // ... + * const MyComponent = ({ dataSources }) => { + * const onLoad = () => { + * console.log(EmbedEvent.Load, {}); + * }; + * + * return ( + * + * ); + * }; + * ``` + * @group Events + */ +var EmbedEvent; +(function (EmbedEvent) { + /** + * Rendering has initialized. + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Init, showLoader) + * //show a loader + * function showLoader() { + * document.getElementById("loader"); + * } + * ``` + * @returns timestamp - The timestamp when the event was generated. + */ + EmbedEvent["Init"] = "init"; + /** + * Authentication has either succeeded or failed. + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.AuthInit, payload => { + * console.log('AuthInit', payload); + * }) + * ``` + * @returns isLoggedIn - A Boolean specifying whether authentication was successful. + */ + EmbedEvent["AuthInit"] = "authInit"; + /** + * The embed object container has loaded. + * @returns timestamp - The timestamp when the event was generated. + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Load, hideLoader) + * //hide loader + * function hideLoader() { + * document.getElementById("loader"); + * } + * ``` + */ + EmbedEvent["Load"] = "load"; + /** + * Data pertaining to an Answer, Liveboard or Spotter visualization is received. + * The event payload includes the raw data of the object. + * @return data - Answer of Liveboard data + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Data, payload => { + * console.log('data', payload); + * }) + * ``` + * @important + */ + EmbedEvent["Data"] = "data"; + /** + * Search query has been updated by the user. + * @version SDK: 1.4.0 | ThoughtSpot: ts7.sep.cl, 8.4.1.sw + * @example + * ```js + * searchEmbed.on(EmbedEvent.QueryChanged, payload => console.log('data', payload)) + * ``` + */ + EmbedEvent["QueryChanged"] = "queryChanged"; + /** + * A drill-down operation has been performed. + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @returns additionalFilters - Any additional filters applied + * @returns drillDownColumns - The columns on which drill down was performed + * @returns nonFilteredColumns - The columns that were not filtered + * @example + * ```js + * searchEmbed.on(EmbedEvent.DrillDown, { + * points: { + * clickedPoint, + * selectedPoints: selectedPoint + * }, + * autoDrillDown: true, + * }) + * ``` + * In this example, `VizPointDoubleClick` event is used for + * triggering the `DrillDown` event when an area or specific + * data point on a table or chart is double-clicked. + * @example + * ```js + * searchEmbed.on(EmbedEvent.VizPointDoubleClick, (payload) => { + * console.log(payload); + * const clickedPoint = payload.data.clickedPoint; + * const selectedPoint = payload.data.selectedPoints; + * console.log('>>> called', clickedPoint); + * embed.trigger(HostEvent.DrillDown, { + * points: { + * clickedPoint, + * selectedPoints: selectedPoint + * }, + * autoDrillDown: true, + * }) + * }) + * ``` + */ + EmbedEvent["Drilldown"] = "drillDown"; + /** + * One or more data sources have been selected. + * @returns dataSourceIds - the list of data sources + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * searchEmbed.on(EmbedEvent.DataSourceSelected, payload => { + * console.log('DataSourceSelected', payload); + * }) + * ``` + */ + EmbedEvent["DataSourceSelected"] = "dataSourceSelected"; + /** + * One or more data columns have been selected. + * @returns columnIds - the list of columns + * @version SDK: 1.10.0 | ThoughtSpot: 8.2.0.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.AddRemoveColumns, payload => { + * console.log('AddRemoveColumns', payload); + * }) + * ``` + */ + EmbedEvent["AddRemoveColumns"] = "addRemoveColumns"; + /** + * A custom action has been triggered. + * @returns actionId - ID of the custom action + * @returns payload {@link CustomActionPayload} - Response payload with the + * Answer or Liveboard data + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.customAction, payload => { + * const data = payload.data; + * if (data.id === 'insert Custom Action ID here') { + * console.log('Custom Action event:', data.embedAnswerData); + * } + * }) + * ``` + */ + EmbedEvent["CustomAction"] = "customAction"; + /** + * Listen to double click actions on a visualization. + * @return ContextMenuInputPoints - Data point that is double-clicked + * @version SDK: 1.5.0 | ThoughtSpot: ts7.oct.cl, 7.2.1 + * @example + * ```js + * LiveboardEmbed.on(EmbedEvent.VizPointDoubleClick, payload => { + * console.log('VizPointDoubleClick', payload); + * }) + * ``` + */ + EmbedEvent["VizPointDoubleClick"] = "vizPointDoubleClick"; + /** + * Listen to clicks on a visualization in a Liveboard or Search result. + * @return viz, clickedPoint - metadata about the point that is clicked + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @important + * @example + * ```js + * embed.on(EmbedEvent.VizPointClick, ({data}) => { + * console.log( + * data.vizId, // viz id + * data.clickedPoint.selectedAttributes[0].value, + * data.clickedPoint.selectedAttributes[0].column.name, + * data.clickedPoint.selectedMeasures[0].value, + * data.clickedPoint.selectedMeasures[0].column.name, + * ) + * }); + * ``` + */ + EmbedEvent["VizPointClick"] = "vizPointClick"; + /** + * Fired when an error occurs in the embedded component. + * + * **Important:** This event fires for many reasons — including internal + * validation warnings (e.g. `HOST_EVENT_VALIDATION`), configuration issues, + * and transient errors that ThoughtSpot already handles gracefully inside the + * iframe. **Do not call `embed.destroy()` or unmount the embed component on + * every error.** Doing so will tear down the iframe and abort all in-flight + * requests, causing the embed to fail entirely. + * + * Only treat the following codes as unrecoverable: + * - `INIT_ERROR` — SDK was not initialised before render + * - `LOGIN_FAILED` — authentication could not be completed + * + * All other error codes should be logged and inspected, not acted upon + * destructively. + * + * **Note:** There is currently no dedicated event for a true unrecoverable + * crash. A future `EmbedEvent.FatalError` event is planned to give customers + * a clean signal for when the embed cannot recover and needs to be torn down. + * + * Error types include: + * `API` - API call failure. + * `FULLSCREEN` - Error when presenting a Liveboard in full screen mode. + * `VALIDATION_ERROR` - Internal host event or configuration validation warning. + * + * For more information, see https://developers.thoughtspot.com/docs/events-app-integration#errorType + * @returns error - An error object or message + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * // Recommended pattern — only destroy on truly fatal errors + * embed.on(EmbedEvent.Error, (error) => { + * const FATAL_CODES = ['INIT_ERROR', 'LOGIN_FAILED']; + * if (FATAL_CODES.includes(error.data?.code)) { + * embed.destroy(); + * return; + * } + * // Log all other errors — do not destroy + * console.warn('Embed error (non-fatal):', error); + * }); + * ``` + * @example + * ```js + * // API error + * SearchEmbed.on(EmbedEvent.Error, (error) => { + * console.log(error); + * // { errorType: "API", message: '...', code: '...' } + * }); + * ``` + */ + EmbedEvent["Error"] = "Error"; + /** + * The embedded object has sent an alert. + * @returns alert - An alert object + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * searchEmbed.on(EmbedEvent.Alert) + * ``` + */ + EmbedEvent["Alert"] = "alert"; + /** + * The ThoughtSpot authentication session has expired. + * @version SDK: 1.4.0 | ThoughtSpot: ts7.sep.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.AuthExpire, showAuthExpired) + * //show auth expired banner + * function showAuthExpired() { + * document.getElementById("authExpiredBanner"); + * } + * ``` + */ + EmbedEvent["AuthExpire"] = "ThoughtspotAuthExpired"; + /** + * ThoughtSpot failed to validate the auth session. + * @hidden + */ + EmbedEvent["AuthFailure"] = "ThoughtspotAuthFailure"; + /** + * ThoughtSpot failed to re validate the auth session. + * @hidden + */ + EmbedEvent["IdleSessionTimeout"] = "IdleSessionTimeout"; + /** + * ThoughtSpot failed to validate the auth session. + * @hidden + */ + EmbedEvent["AuthLogout"] = "ThoughtspotAuthLogout"; + /** + * The height of the embedded Liveboard or visualization has been computed. + * @returns data - The height of the embedded Liveboard or visualization + * @hidden + */ + EmbedEvent["EmbedHeight"] = "EMBED_HEIGHT"; + /** + * The center of visible iframe viewport is calculated. + * @returns data - The center of the visible Iframe viewport. + * @hidden + */ + EmbedEvent["EmbedIframeCenter"] = "EmbedIframeCenter"; + /** + * Emitted when the **Get Data** action is initiated. + * Applicable to `SearchBarEmbed` only. + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + * @example + * ```js + * searchbarEmbed.on(EmbedEvent.GetDataClick) + * .then(data => { + * console.log('Answer Data:', data); + * }) + * ``` + */ + EmbedEvent["GetDataClick"] = "getDataClick"; + /** + * Detects the route change. + * @version SDK: 1.7.0 | ThoughtSpot: 8.0.0.cl, 8.4.1.sw + * @example + * ```js + * searchEmbed.on(EmbedEvent.RouteChange, payload => + * console.log('data', payload)) + * ``` + */ + EmbedEvent["RouteChange"] = "ROUTE_CHANGE"; + /** + * The v1 event type for Data + * @hidden + */ + EmbedEvent["V1Data"] = "exportVizDataToParent"; + /** + * Emitted when the embed does not have cookie access. This happens + * when third-party cookies are blocked by Safari or other + * web browsers. `NoCookieAccess` can trigger. + * @example + * ```js + * appEmbed.on(EmbedEvent.NoCookieAccess) + * ``` + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 7.2.1.sw + */ + EmbedEvent["NoCookieAccess"] = "noCookieAccess"; + /** + * Emitted when SAML is complete + * @private + * @hidden + */ + EmbedEvent["SAMLComplete"] = "samlComplete"; + /** + * Emitted when any modal is opened in the app + * @version SDK: 1.6.0 | ThoughtSpot: ts8.nov.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.DialogOpen, payload => { + * console.log('dialog open', payload); + * }) + * ``` + */ + EmbedEvent["DialogOpen"] = "dialog-open"; + /** + * Emitted when any modal is closed in the app + * @version SDK: 1.6.0 | ThoughtSpot: ts8.nov.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.DialogClose, payload => { + * console.log('dialog close', payload); + * }) + * ``` + */ + EmbedEvent["DialogClose"] = "dialog-close"; + /** + * Emitted when the Liveboard shell loads. + * You can use this event as a hook to trigger + * other events on the rendered Liveboard. + * @version SDK: 1.9.1 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.LiveboardRendered, payload => { + console.log('Liveboard is rendered', payload); + }) + * ``` + * The following example shows how to trigger + * `SetVisibleVizs` event using LiveboardRendered embed event: + * @example + * ```js + * const embedRef = useEmbedRef(); + * const onLiveboardRendered = () => { + * embed.trigger(HostEvent.SetVisibleVizs, ['viz1', 'viz2']); + * }; + * ``` + */ + EmbedEvent["LiveboardRendered"] = "PinboardRendered"; + /** + * Emits all events. + * @version SDK: 1.10.0 | ThoughtSpot: 8.2.0.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.ALL, payload => { + * console.log('Embed Events', payload) + * }) + * ``` + */ + EmbedEvent["ALL"] = "*"; + /** + * Emitted when an Answer is saved in the app. + * Use start:true to subscribe to when save is initiated, or end:true to subscribe to when save is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //Emit when action starts + * searchEmbed.on(EmbedEvent.Save, payload => { + * console.log('Save', payload) + * }, { + * start: true + * }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.Save, payload => { + * console.log('Save', payload) + * }) + * ``` + */ + EmbedEvent["Save"] = "save"; + /** + * Emitted when the download action is triggered on an Answer. + * + * **Note**: This event is deprecated in v1.21.0. + * To fire an event when a download action is initiated on a chart or table, + * use `EmbedEvent.DownloadAsPng`, `EmbedEvent.DownloadAsPDF`, + * `EmbedEvent.DownloadAsCSV`, or `EmbedEvent.DownloadAsXLSX` + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Download, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }) + * ``` + */ + EmbedEvent["Download"] = "download"; + /** + * Emitted when the download action is triggered on an Answer. + * Use start:true to subscribe to when download is initiated, or end:true to + * subscribe to when download is completed. Default is end:true. + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.4.0.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.DownloadAsPng, payload => { + * console.log('download PNG', payload)}, {start: true }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.DownloadAsPng, payload => { + * console.log('download PNG', payload)}) + * ``` + */ + EmbedEvent["DownloadAsPng"] = "downloadAsPng"; + /** + * Emitted when the Download as PDF action is triggered on an Answer + * Use start:true to subscribe to when download as PDF is initiated, or end:true to + * subscribe to when download as PDF is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.DownloadAsPdf, payload => { + * console.log('download PDF', payload)}, {start: true }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.DownloadAsPdf, payload => { + * console.log('download PDF', payload)}) + * ``` + */ + EmbedEvent["DownloadAsPdf"] = "downloadAsPdf"; + /** + * Emitted when the Download as CSV action is triggered on an Answer. + * Use start:true to subscribe to when download as CSV is initiated, or end:true to + * subscribe to when download as CSV is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.DownloadAsCSV, payload => { + * console.log('download CSV', payload)}, {start: true }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.DownloadAsCSV, payload => { + * console.log('download CSV', payload)}) + * ``` + */ + EmbedEvent["DownloadAsCsv"] = "downloadAsCsv"; + /** + * Emitted when the Download as XLSX action is triggered on an Answer. + * Use start:true to subscribe to when download as XLSX is initiated, or end:true to + * subscribe to when download as XLSX is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.DownloadAsXlsx, payload => { + * console.log('download Xlsx', payload)}, { start: true }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.DownloadAsXlsx, payload => { + * console.log('download Xlsx', payload)}) + * ``` + */ + EmbedEvent["DownloadAsXlsx"] = "downloadAsXlsx"; + /** + * Emitted when the Download Liveboard as Continuous PDF action is triggered + * on a Liveboard. + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.DownloadLiveboardAsContinuousPDF, payload => { + * console.log('download liveboard as continuous PDF', payload)}) + * ``` + */ + EmbedEvent["DownloadLiveboardAsContinuousPDF"] = "downloadLiveboardAsContinuousPDF"; + /** + * Emitted when an Answer is deleted in the app + * Use start:true to subscribe to when delete is initiated, or end:true to subscribe + * to when delete is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * appEmbed.on(EmbedEvent.AnswerDelete, payload => { + * console.log('delete answer', payload)}, {start: true }) + * //trigger when action is completed + * appEmbed.on(EmbedEvent.AnswerDelete, payload => { + * console.log('delete answer', payload)}) + * ``` + */ + EmbedEvent["AnswerDelete"] = "answerDelete"; + /** + * Emitted when the AI Highlights action is triggered on a Liveboard + * @version SDK: 1.44.0 | ThoughtSpot: 10.15.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.AIHighlights, (payload) => { + * console.log('AI Highlights', payload); + * }) + * ``` + */ + EmbedEvent["AIHighlights"] = "AIHighlights"; + /** + * Emitted when a user initiates the Pin action to + * add an Answer to a Liveboard. + * Use start:true to subscribe to when pin is initiated, or end:true to subscribe to + * when pin is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.Pin, payload => { + * console.log('pin', payload) + * }, { + * start: true + * }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.Pin, payload => { + * console.log('pin', payload) + * }) + * ``` + */ + EmbedEvent["Pin"] = "pin"; + /** + * Emitted when SpotIQ analysis is triggered + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.SpotIQAnalyze, payload => { + * console.log('SpotIQAnalyze', payload) + * }, { + * start: true + * }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.SpotIQAnalyze, payload => { + * console.log('SpotIQ analyze', payload) + * }) + * ``` + */ + EmbedEvent["SpotIQAnalyze"] = "spotIQAnalyze"; + /** + * Emitted when a user shares an object with another user or group + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.Share, payload => { + * console.log('Share', payload) + * }, { + * start: true + * }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.Share, payload => { + * console.log('Share', payload) + * }) + * ``` + */ + EmbedEvent["Share"] = "share"; + /** + * Emitted when a user clicks the **Include** action to include a specific value or + * data on a chart or table. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.DrillInclude, payload => { + * console.log('Drill include', payload); + * }) + * ``` + */ + EmbedEvent["DrillInclude"] = "context-menu-item-include"; + /** + * Emitted when a user clicks the **Exclude** action to exclude a specific value or + * data on a chart or table + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.DrillExclude, payload => { + * console.log('Drill exclude', payload); + * }) + * ``` + */ + EmbedEvent["DrillExclude"] = "context-menu-item-exclude"; + /** + * Emitted when a column value is copied in the embedded app. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * searchEmbed.on(EmbedEvent.CopyToClipboard, payload => { + * console.log('copy to clipboard', payload); + * }) + * ``` + */ + EmbedEvent["CopyToClipboard"] = "context-menu-item-copy-to-clipboard"; + /** + * Emitted when a user clicks the **Update TML** action on + * embedded Liveboard. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.UpdateTML) + * }) + * ``` + */ + EmbedEvent["UpdateTML"] = "updateTSL"; + /** + * Emitted when a user clicks the **Edit TML** action + * on an embedded Liveboard. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.EditTML, payload => { + * console.log('Edit TML', payload); + * }) + * ``` + */ + EmbedEvent["EditTML"] = "editTSL"; + /** + * Emitted when the **Export TML** action is triggered on an + * an embedded object in the app + * Use start:true to subscribe to when export is initiated, or end:true to subscribe + * to when export is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.ExportTML, payload => { + * console.log('Export TML', payload)}, { start: true }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.ExportTML, payload => { + * console.log('Export TML', payload)}) + * ``` + */ + EmbedEvent["ExportTML"] = "exportTSL"; + /** + * Emitted when an Answer is saved as a View. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.SaveAsView, payload => { + * console.log('View', payload); + * }) + * ``` + */ + EmbedEvent["SaveAsView"] = "saveAsView"; + /** + * Emitted when the user creates a copy of an Answer. + * Use start:true to subscribe to when copy and edit is initiated, or end:true to + * subscribe to when copy and edit is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * appEmbed.on(EmbedEvent.CopyAEdit, payload => { + * console.log('Copy and edit', payload)}, {start: true }) + * //emit when action ends + * appEmbed.on(EmbedEvent.CopyAEdit, payload => { + * console.log('Copy and edit', payload)}) + * ``` + */ + EmbedEvent["CopyAEdit"] = "copyAEdit"; + /** + * Emitted when a user clicks *Show underlying data* on an Answer. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.ShowUnderlyingData, payload => { + * console.log('show data', payload); + * }) + * ``` + */ + EmbedEvent["ShowUnderlyingData"] = "showUnderlyingData"; + /** + * Emitted when an Answer is switched to a chart or table view. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * searchEmbed.on(EmbedEvent.AnswerChartSwitcher, payload => { + * console.log('switch view', payload); + * }) + * ``` + */ + EmbedEvent["AnswerChartSwitcher"] = "answerChartSwitcher"; + /** + * Internal event to communicate the initial settings back to the ThoughtSpot app + * @hidden + */ + EmbedEvent["APP_INIT"] = "appInit"; + /** + * Internal event to clear the cached info + * @hidden + */ + EmbedEvent["CLEAR_INFO_CACHE"] = "clearInfoCache"; + /** + * Emitted when a user clicks **Show Liveboard details** on a Liveboard + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.LiveboardInfo, payload => { + * console.log('Liveboard details', payload); + * }) + * ``` + */ + EmbedEvent["LiveboardInfo"] = "pinboardInfo"; + /** + * Emitted when a user clicks on the Favorite icon on a Liveboard + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.AddToFavorites, payload => { + * console.log('favorites', payload); + * }) + * ``` + */ + EmbedEvent["AddToFavorites"] = "addToFavorites"; + /** + * Emitted when a user clicks **Schedule** on a Liveboard + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Schedule, payload => { + * console.log('Liveboard schedule', payload); + * }) + * ``` + */ + EmbedEvent["Schedule"] = "subscription"; + /** + * Emitted when a user clicks **Edit** on a Liveboard or visualization + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Edit, payload => { + * console.log('Liveboard edit', payload); + * }) + * ``` + */ + EmbedEvent["Edit"] = "edit"; + /** + * Emitted when a user clicks *Make a copy* on a Liveboard + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.MakeACopy, payload => { + * console.log('Copy', payload); + * }) + * ``` + */ + EmbedEvent["MakeACopy"] = "makeACopy"; + /** + * Emitted when a user clicks **Present** on a Liveboard or visualization + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Present) + * ``` + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Present, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * }) + * ``` + */ + EmbedEvent["Present"] = "present"; + /** + * Emitted when a user clicks **Delete** on a visualization + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Delete, + * {vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + */ + EmbedEvent["Delete"] = "delete"; + /** + * Emitted when a user clicks Manage schedules on a Liveboard + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SchedulesList) + * ``` + */ + EmbedEvent["SchedulesList"] = "schedule-list"; + /** + * Emitted when a user clicks **Cancel** in edit mode on a Liveboard + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Cancel) + * ``` + */ + EmbedEvent["Cancel"] = "cancel"; + /** + * Emitted when a user clicks **Explore** on a visualization + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Explore, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + */ + EmbedEvent["Explore"] = "explore"; + /** + * Emitted when a user clicks **Copy link** action on a visualization. + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.CopyLink, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + */ + EmbedEvent["CopyLink"] = "embedDocument"; + /** + * Emitted when a user interacts with cross filters on a + * visualization or Liveboard. + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.0.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.CrossFilterChanged, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + */ + EmbedEvent["CrossFilterChanged"] = "cross-filter-changed"; + /** + * Emitted when a user right clicks on a visualization (chart or table) + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.0.sw + * @example + * ```js + * LiveboardEmbed.on(EmbedEvent.VizPointRightClick, payload => { + * console.log('VizPointClick', payload) + * }) + * ``` + */ + EmbedEvent["VizPointRightClick"] = "vizPointRightClick"; + /** + * Emitted when a user clicks **Insert to slide** on a visualization + * @hidden + */ + EmbedEvent["InsertIntoSlide"] = "insertInToSlide"; + /** + * Emitted when a user changes any filter on a Liveboard. + * Returns filter type and name, column name and ID, and runtime + * filter details. + * @example + * + * ```js + * LiveboardEmbed.on(EmbedEvent.FilterChanged, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.23.0 | ThoughtSpot: 9.4.0.cl, 9.5.0.sw + */ + EmbedEvent["FilterChanged"] = "filterChanged"; + /** + * Emitted when a user updates a connection on the **Data** page + * @version SDK: 1.27.0 | ThoughtSpot: 9.8.0.cl, 9.8.0.sw + */ + EmbedEvent["UpdateConnection"] = "updateConnection"; + /** + * Emitted when a user updates a connection on the **Data** page + * @version SDK: 1.27.0 | ThoughtSpot: 9.8.0.cl, 9.8.0.sw + */ + EmbedEvent["CreateConnection"] = "createConnection"; + /** + * Emitted when name, status (private or public) or filter values of a + * Personalised view is updated. + * This event is deprecated. Use {@link EmbedEvent.UpdatePersonalizedView} instead. + * @returns viewName: string + * @returns viewId: string + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + EmbedEvent["UpdatePersonalisedView"] = "updatePersonalisedView"; + /** + * Emitted when name, status (private or public) or filter values of a + * Personalized view is updated. + * @returns viewName: string + * @returns viewId: string + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + EmbedEvent["UpdatePersonalizedView"] = "updatePersonalisedView"; + /** + * Emitted when a Personalised view is saved. + * This event is deprecated. Use {@link EmbedEvent.SavePersonalizedView} instead. + * @returns viewName: string + * @returns viewId: string + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + EmbedEvent["SavePersonalisedView"] = "savePersonalisedView"; + /** + * Emitted when a Personalized view is saved. + * @returns viewName: string + * @returns viewId: string + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + EmbedEvent["SavePersonalizedView"] = "savePersonalisedView"; + /** + * Emitted when a Liveboard is reset. + * @returns viewName: string + * @returns viewId: string + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + */ + EmbedEvent["ResetLiveboard"] = "resetLiveboard"; + /** + * Emitted when a PersonalisedView is deleted. + * This event is deprecated. Use {@link EmbedEvent.DeletePersonalizedView} instead. + * @returns views: string[] + * @returns liveboardId: string + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + EmbedEvent["DeletePersonalisedView"] = "deletePersonalisedView"; + /** + * Emitted when a PersonalizedView is deleted. + * @returns views: string[] + * @returns liveboardId: string + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + EmbedEvent["DeletePersonalizedView"] = "deletePersonalisedView"; + /** + * Emitted when a user selects a different Personalized View or + * resets to the original/default view on a Liveboard. + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.ChangePersonalizedView, (data) => { + * console.log(data.viewName); // 'Q4 Revenue' or 'Original View' + * console.log(data.viewId); // '2a021a12-...' or null (default) + * console.log(data.liveboardId); // 'abc123...' + * console.log(data.isPublic); // true | false + * }) + * ``` + * @returns viewName: string - Name of the selected view, + * or 'Original View' when reset to default. + * @returns viewId: string | null - GUID of the selected view, + * or null when reset to default. + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + EmbedEvent["ChangePersonalizedView"] = "changePersonalisedView"; + /** + * Emitted when a user creates a Worksheet. + * @version SDK: 1.27.0 | ThoughtSpot: 9.8.0.cl, 9.8.0.sw + */ + EmbedEvent["CreateWorksheet"] = "createWorksheet"; + /** + * Emitted when the *Ask Sage* is initialized. + * @returns viewName: string + * @returns viewId: string + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.29.0 | ThoughtSpot Cloud: 9.12.0.cl + */ + EmbedEvent["AskSageInit"] = "AskSageInit"; + /** + * Emitted when a Liveboard or visualization is renamed. + * @version SDK: 1.28.0 | ThoughtSpot: 9.10.5.cl, 10.1.0.sw + */ + EmbedEvent["Rename"] = "rename"; + /** + * + * This event allows developers to intercept search execution + * and implement logic that decides whether Search Data should return + * data or block the search operation. + * + * **Prerequisite**: Set`isOnBeforeGetVizDataInterceptEnabled` to `true` + * to ensure that `EmbedEvent.OnBeforeGetVizDataIntercept` is emitted + * when the embedding application user tries to run a search query. + * + * This framework applies only to `AppEmbed` and `SearchEmbed`. + * @param - Includes the following parameters: + * - `payload`: The payload received from the embed related to the Data API call. + * - `responder`: Contains elements that let developers define whether ThoughtSpot + * will run or block the search operation, and if blocked, which error message to + * provide. + * - `execute` - When `execute` returns `true`, the search is run. + * When `execute` returns `false`, the search is not executed. + * - `error` - Developers can customize the user-facing error message when `execute` + * is `false` by using the `error` parameters in `responder`. + * - `errorText` - The error message text shown to the user. + * @version SDK: 1.29.0 | ThoughtSpot: 10.3.0.cl + * @example + * + * This example blocks search operation and returns a custom error message: + * ```js + * embed.on(EmbedEvent.OnBeforeGetVizDataIntercept, (payload, responder) => { + * responder({ + * data: { + * execute: false, + * error: { + * // Provide a custom error message to explain why the search did not run. + * errorText: 'This search query cannot be run. Please contact your administrator for more details.', + * }, + * }, + * }); + * }) + * ``` + * @example + * + * This example allows the search operation to run + * unless the query contains both `sales` and `county`, + * and returns a custom error message if the query is rejected: + * ```js + * embed.on(EmbedEvent.OnBeforeGetVizDataIntercept, (payload, responder) => { + * // Record the search query submitted by the end user. + * const query = payload.data.data.answer.search_query; + * + * responder({ + * data: { + * // Returns true as long as the query does not include both `sales` and `county`. + * execute: !(query.includes('sales') && query.includes('county')), + * error: { + * // Provide a custom error message when the query is blocked by your logic. + * errorText: + * "You can't use this query: " + * + query + * + ". The 'sales' measure can never be used at the 'county' level. " + * + "Please try another measure or remove 'county' from your search.", + * }, + * }, + * }); + * }) + * ``` + */ + EmbedEvent["OnBeforeGetVizDataIntercept"] = "onBeforeGetVizDataIntercept"; + /** + * Emitted when parameter changes in an Answer + * or Liveboard. + * ```js + * liveboardEmbed.on(EmbedEvent.ParameterChanged, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.29.0 | ThoughtSpot: 10.3.0.cl + */ + EmbedEvent["ParameterChanged"] = "parameterChanged"; + /** + * Emits when a table visualization is rendered in + * the ThoughtSpot embedded app. + * + * You can also use this event as a hook to trigger host events + * such as `HostEvent.TransformTableVizData` on the table visualization. + * The event payload contains the data used in the rendered table. + * You can extract the relevant data from the payload + * stored in `payload.data.data.columnDataLite`. + * + * `columnDataLite` is a multidimensional array that contains + * data values for each column, which was used in the query to + * generate the table visualization. To find and modify specific cell data, + * you can either loop through the array or directly access a cell if + * you know its position and data index. + * + * In the following code sample, the first cell in the first column + * (`columnDataLite[0].dataValue[0]`) is set to `new fob`. + * Note that any changes made to the data in the payload will only update the + * visual presentation and do not affect the underlying data. + * To persist data value modifications after a reload or during chart + * interactions such as drill down, ensure that the modified + * payload in the `columnDataLite` is passed on to + * `HostEvent.TransformTableVizData` and trigger an update to + * the table visualization. + * + * If the Row-Level Security (RLS) rules are applied on the + * Model, exercise caution when changing column + * or table cell values to maintain data security. + * + * @example + * ```js + * searchEmbed.on(EmbedEvent.TableVizRendered, (payload) => { + * console.log(payload); + * const columnDataLite = payload.data.data.columnDataLite; + * columnDataLite[0].dataValue[0]="new fob"; + * console.log('>>> new Data', columnDataLite); + * searchEmbed.trigger(HostEvent.TransformTableVizData, columnDataLite); + * }) + * ``` + * @version SDK: 1.37.0 | ThoughtSpot: 10.8.0.cl + */ + EmbedEvent["TableVizRendered"] = "TableVizRendered"; + /** + * Emitted when the liveboard is created from pin modal or Liveboard list page. + * You can use this event as a hook to trigger + * other events on liveboard creation. + * + * ```js + * liveboardEmbed.on(EmbedEvent.CreateLiveboard, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.37.0 | ThoughtSpot: 10.8.0.cl + */ + EmbedEvent["CreateLiveboard"] = "createLiveboard"; + /** + * Emitted when a user creates a Model. + * @version SDK: 1.37.0 | ThoughtSpot: 10.8.0.cl + */ + EmbedEvent["CreateModel"] = "createModel"; + /** + * @hidden + * Emitted when a user exits present mode. + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + EmbedEvent["ExitPresentMode"] = "exitPresentMode"; + /** + * Emitted when a user requests the full height lazy load data. + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + * @hidden + */ + EmbedEvent["RequestVisibleEmbedCoordinates"] = "requestVisibleEmbedCoordinates"; + /** + * Emitted when Spotter response is text data + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterData, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + EmbedEvent["SpotterData"] = "SpotterData"; + /** + * Emitted when user opens up the data source preview modal in Spotter embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.PreviewSpotterData, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + EmbedEvent["PreviewSpotterData"] = "PreviewSpotterData"; + /** + * Emitted when user opens up the Add to Coaching modal on any visualization in Spotter Embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.AddToCoaching, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.45.0 | ThoughtSpot: 26.2.0.cl + */ + EmbedEvent["AddToCoaching"] = "addToCoaching"; + /** + * Emitted when user opens up the data model instructions modal in Spotter embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.DataModelInstructions, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + */ + EmbedEvent["DataModelInstructions"] = "DataModelInstructions"; + /** + * Emitted when the Spotter query is triggered in Spotter embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterQueryTriggered, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + EmbedEvent["SpotterQueryTriggered"] = "SpotterQueryTriggered"; + /** + * Emitted when the last Spotter query is edited in Spotter embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.LastPromptEdited, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + EmbedEvent["LastPromptEdited"] = "LastPromptEdited"; + /** + * Emitted when the last Spotter query is deleted in Spotter embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.LastPromptDeleted, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + EmbedEvent["LastPromptDeleted"] = "LastPromptDeleted"; + /** + * Emitted when the conversation is reset in Spotter embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.ResetSpotterConversation, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + EmbedEvent["ResetSpotterConversation"] = "ResetSpotterConversation"; + /** + * Emitted when the *Spotter* is initialized. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterInit, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.41.0 | ThoughtSpot: 10.12.0.cl + */ + EmbedEvent["SpotterInit"] = "spotterInit"; + /** + * Emitted when a *Spotter* conversation has been successfully created. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterLoadComplete, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.44.0 | ThoughtSpot: 26.2.0.cl + */ + EmbedEvent["SpotterLoadComplete"] = "spotterLoadComplete"; + /** + * @hidden + * Triggers when the embed listener is ready to receive events. + * This is used to trigger events after the embed container is loaded. + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.EmbedListenerReady, () => { + * console.log('EmbedListenerReady'); + * }) + * ``` + */ + EmbedEvent["EmbedListenerReady"] = "EmbedListenerReady"; + /** + * Emitted when the organization is switched. + * @example + * ```js + * appEmbed.on(EmbedEvent.OrgSwitched, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.41.0 | ThoughtSpot: 10.12.0.cl + */ + EmbedEvent["OrgSwitched"] = "orgSwitched"; + /** + * Emitted when the user intercepts a URL. + * + * Supported on all embed types. + * + * @example + * + * ```js + * embed.on(EmbedEvent.ApiIntercept, (payload, responder) => { + * console.log('payload', payload); + * responder({ + * data: { + * execute: false, + * error: { + * errorText: 'Error Occurred', + * } + * } + * }) + * }) + * ``` + * + * ```js + * // We can also send a response for the intercepted api + * embed.on(EmbedEvent.ApiIntercept, (payload, responder) => { + * console.log('payload', payload); + * responder({ + * data: { + * execute: false, + * response: { + * body: { + * data: { + * // Some api response + * }, + * } + * } + * } + * }) + * }) + * + * // here embed will use the response from the responder as the response for the api + * ``` + * + * ```js + * // We can also send error in response for the intercepted api + * embed.on(EmbedEvent.ApiIntercept, (payload, responder) => { + * console.log('payload', payload); + * responder({ + * data: { + * execute: false, + * response: { + * body: { + * errors: [{ + * title: 'Error Occurred', + * description: 'Error Description', + * isUserError: true, + * }], + * data: {}, + * }, + * } + * } + * }) + * }) + * ``` + * @version SDK: 1.43.0 | ThoughtSpot: 10.15.0.cl + */ + EmbedEvent["ApiIntercept"] = "ApiIntercept"; + /** + * Emitted when a Spotter conversation is renamed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterConversationRenamed, (payload) => { + * console.log('Conversation renamed', payload); + * // payload: { convId: string, oldTitle: string, newTitle: string } + * }) + * ``` + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + */ + EmbedEvent["SpotterConversationRenamed"] = "spotterConversationRenamed"; + /** + * Emitted when a Spotter conversation is deleted. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterConversationDeleted, (payload) => { + * console.log('Conversation deleted', payload); + * // payload: { convId: string, title: string } + * }) + * ``` + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + */ + EmbedEvent["SpotterConversationDeleted"] = "spotterConversationDeleted"; + /** + * Emitted when a Spotter conversation is selected/clicked. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterConversationSelected, (payload) => { + * console.log('Conversation selected', payload); + * // payload: { convId: string, title: string, worksheetId: string } + * }) + * ``` + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + */ + EmbedEvent["SpotterConversationSelected"] = "spotterConversationSelected"; + /** + * @hidden + * Emitted when the auth token is about to get expired and needs to be refreshed. + * @example + * ```js + * embed.on(EmbedEvent.RefreshAuthToken, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.45.2 | ThoughtSpot: 26.3.0.cl + */ + EmbedEvent["RefreshAuthToken"] = "RefreshAuthToken"; + /** + * Triggered whenever the page context changes, returning the current context along with the navigation stack. + * @example + * ```js + * embed.on(EmbedEvent.EmbedPageContextChanged, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.47.2 | ThoughtSpot: 26.3.0.cl + */ + EmbedEvent["EmbedPageContextChanged"] = "EmbedPageContextChanged"; + /** + * Represents a special embed event that is triggered whenever any host event is subscribed. + * + * You can listen to this event when you need to dispatch a host event during load or render, + * particularly in situations where timing issues may occur. + * + * @example + * ```js + * embed.on(`${HostEvent.Save} Subscribed`, () => { + * // make action + * }); + * ``` + * + * @example + * ```js + * embed.on(subscribedEvent(HostEvent.Save), () => { + * // make action + * }); + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.4.0.cl + */ + EmbedEvent["Subscribed"] = "Subscribed"; + /** + * Emitted when a user clicks the **Send Test Email** button in the + * Liveboard schedule modal. Requires `isSendNowLiveboardSchedulingEnabled` + * to be enabled. + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SendTestScheduleEmail, (payload) => { + * console.log('Send test email', payload); + * // payload: { liveboardId: string, sendToSelf: boolean } + * }) + * ``` + * @version SDK: 1.48.0 | ThoughtSpot Cloud: 26.5.0.cl + */ + EmbedEvent["SendTestScheduleEmail"] = "sendTestScheduleEmail"; + /** + * Emitted when the SpotterViz panel mounts in embed mode. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizInit, (payload) => { + * console.log('SpotterViz initialized', payload); + * // payload: { liveboardId: string } + * }) + * ``` + */ + EmbedEvent["SpotterVizInit"] = "SpotterVizInit"; + /** + * Emitted when the user submits a prompt in the SpotterViz panel. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizQueryTriggered, (payload) => { + * console.log('SpotterViz query triggered', payload); + * // payload: { query: string, sessionId: string } + * }) + * ``` + */ + EmbedEvent["SpotterVizQueryTriggered"] = "SpotterVizQueryTriggered"; + /** + * Emitted when the SpotterViz agent finishes responding. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizResponseComplete, (payload) => { + * console.log('SpotterViz response complete', payload); + * // payload: { sessionId: string, messageId: string } + * }) + * ``` + */ + EmbedEvent["SpotterVizResponseComplete"] = "SpotterVizResponseComplete"; + /** + * Emitted when a checkpoint is created in the SpotterViz panel. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizCheckpointCreated, (payload) => { + * console.log('SpotterViz checkpoint created', payload); + * // payload: { checkpointId: string, source: string, label: string } + * }) + * ``` + */ + EmbedEvent["SpotterVizCheckpointCreated"] = "SpotterVizCheckpointCreated"; + /** + * Emitted when a checkpoint is restored in the SpotterViz panel. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizCheckpointRestored, (payload) => { + * console.log('SpotterViz checkpoint restored', payload); + * // payload: { checkpointId: string, newGenNumber: number } + * }) + * ``` + */ + EmbedEvent["SpotterVizCheckpointRestored"] = "SpotterVizCheckpointRestored"; + /** + * Emitted when an error occurs in the SpotterViz panel. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizError, (payload) => { + * console.log('SpotterViz error', payload); + * }) + * ``` + */ + EmbedEvent["SpotterVizError"] = "SpotterVizError"; + /** + * Emitted when the SpotterViz panel is closed. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizClosed, (payload) => { + * console.log('SpotterViz panel closed', payload); + * }) + * ``` + */ + EmbedEvent["SpotterVizClosed"] = "SpotterVizClosed"; + /** + * Emitted when a user clicks the **Refresh** button in the + * Liveboard header. Requires `enableLiveboardDataCache` + * to be enabled. + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.RefreshLiveboardBrowserCache, (payload) => { + * console.log('Liveboard browser cache refreshed', payload); + * // payload: { liveboardId: string } + * }) + * ``` + * @version SDK: 1.49.0 | ThoughtSpot Cloud: 26.6.0.cl + */ + EmbedEvent["RefreshLiveboardBrowserCache"] = "refreshLiveboardBrowserCache"; +})(EmbedEvent || (EmbedEvent = {})); +/** + * Event types that can be triggered by the host application + * to the embedded ThoughtSpot app. + * + * To trigger an event use the corresponding + * {@link LiveboardEmbed.trigger} or {@link AppEmbed.trigger} or {@link + * SearchEmbed.trigger} method. + * @example + * ```js + * import { HostEvent } from '@thoughtspot/visual-embed-sdk'; + * // Or + * // const { HostEvent } = window.tsembed; + * + * // create the liveboard embed. + * + * liveboardEmbed.trigger(HostEvent.UpdateRuntimeFilters, [ + * { columnName: 'state', operator: RuntimeFilterOp.EQ, values: ["california"]} + * ]); + * ``` + * @example + * If using React components to embed, use the format shown in this example: + * + * ```js + * const selectVizs = () => { + * embedRef.current.trigger(HostEvent.SetVisibleVizs, [ + * "715e4613-c891-4884-be44-aa8d13701c06", + * "3f84d633-e325-44b2-be25-c6650e5a49cf" + * ]); + * }; + * ``` + * + * + * You can also attach an Embed event to a Host event to trigger + * a specific action as shown in this example: + * @example + * ```js + * const EmbeddedComponent = () => { + * const embedRef = useRef(null); // import { useRef } from react + * const onLiveboardRendered = () => { + * embedRef.current.trigger(HostEvent.SetVisibleVizs, ['viz1', 'viz2']); + * }; + * + * return ( + * + * ); + * } + * ``` + * + * **Context Parameter (SDK: 1.45.2+)** + * + * Starting from SDK version 1.45.2 | ThoughtSpot: 26.3.0.cl, you can optionally pass a + * `ContextType` as the third parameter to the `trigger` method to specify the context + * from which the event is triggered. This helps ThoughtSpot understand the current page + * context (Search, Answer, Liveboard, or Spotter) for better event handling. + * + * @example + * ```js + * import { HostEvent, ContextType } from '@thoughtspot/visual-embed-sdk'; + * + * // Trigger Pin event with Search context + * appEmbed.trigger(HostEvent.Pin, { + * vizId: "123", + * liveboardId: "456" + * }, ContextType.Search); + * ``` + * + * @group Events + */ +var HostEvent; +(function (HostEvent) { + /** + * Triggers a search operation with the search tokens specified in + * the search query string. + * Supported in `AppEmbed` and `SearchEmbed` deployments. + * Includes the following properties: + * @param - Includes the following keys: + * - `searchQuery`: Query string with search tokens. + * - `dataSources`: Data source GUID to search on. + * Although an array, only a single source is supported. + * - `execute`: Executes search and updates the existing query. + * @example + * ```js + * searchEmbed.trigger(HostEvent.Search, { + searchQuery: "[sales] by [item type]", + dataSources: ["cd252e5c-b552-49a8-821d-3eadaa049cca"], + execute: true + }); + * ``` + * @example + * ```js + * // Trigger search from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Search, { + * searchQuery: "[revenue] by [region]", + * dataSources: ["cd252e5c-b552-49a8-821d-3eadaa049cca"], + * execute: true + * }, ContextType.Search); + * ``` + */ + HostEvent["Search"] = "search"; + /** + * Triggers a drill on certain points of the specified column + * Includes the following properties: + * @param - Includes the following keys: + * - `points`: An object containing `selectedPoints` and/or `clickedPoint` + * to drill to. For example, `{ selectedPoints: [] }`. + * - `columnGuid`: Optional. GUID of the column to drill by. If not provided, + * it will auto drill by the configured column. + * - `autoDrillDown`: Optional. If `true`, the drill down will be done automatically + * on the most popular column. + * - `vizId` (TS >= 9.8.0): Optional. The GUID of the visualization to drill in case + * of a Liveboard. In Spotter embed, `vizId` refers to the Answer ID and is + * **required**. + * @example + * ```js + * searchEmbed.on(EmbedEvent.VizPointDoubleClick, (payload) => { + * console.log(payload); + * const clickedPoint = payload.data.clickedPoint; + * const selectedPoint = payload.data.selectedPoints; + * console.log('>>> called', clickedPoint); + * searchEmbed.trigger(HostEvent.DrillDown, { + * points: { + * clickedPoint, + * selectedPoints: selectedPoint + * }, + * autoDrillDown: true, + * }); + * }) + * ``` + * @example + * ```js + * // Works with TS 9.8.0 and above + * + * liveboardEmbed.on(EmbedEvent.VizPointDoubleClick, (payload) => { + * console.log(payload); + * const clickedPoint = payload.data.clickedPoint; + * const selectedPoint = payload.data.selectedPoints; + * console.log('>>> called', clickedPoint); + * liveboardEmbed.trigger(HostEvent.DrillDown, { + * points: { + * clickedPoint, + * selectedPoints: selectedPoint + * }, + * columnGuid: "", + * vizId: payload.data.vizId + * }); + * }) + * ``` + * @example + * ```js + * // Drill down from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.DrillDown, { + * points: { clickedPoint, selectedPoints }, + * autoDrillDown: true + * }, ContextType.Answer); + * ``` + * @example + * ```js + * // Drill down from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.DrillDown, { + * points: { clickedPoint, selectedPoints }, + * columnGuid: "column-guid" + * }, ContextType.Search); + * ``` + * @version SDK: 1.5.0 | ThoughtSpot: ts7.oct.cl, 7.2.1 + */ + HostEvent["DrillDown"] = "triggerDrillDown"; + /** + * Apply filters + * @hidden + */ + HostEvent["Filter"] = "filter"; + /** + * Reload the Answer or visualization + * @hidden + */ + HostEvent["Reload"] = "reload"; + /** + * Get iframe URL for the current embed view. + * @example + * ```js + * const url = embed.trigger(HostEvent.GetIframeUrl); + * console.log("iFrameURL",url); + * ``` + * @example + * ```js + * // Get iframe URL from specific context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * const url = await appEmbed.trigger(HostEvent.GetIframeUrl, {}, ContextType.Answer); + * console.log("iFrameURL", url); + * ``` + * @version SDK: 1.35.0 | ThoughtSpot: 10.4.0.cl + */ + HostEvent["GetIframeUrl"] = "GetIframeUrl"; + /** + * Display specific visualizations on a Liveboard. + * @param - An array of GUIDs of the visualization to show. The visualization IDs not passed + * in this parameter will be hidden. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SetVisibleVizs, [ + * '730496d6-6903-4601-937e-2c691821af3c', + * 'd547ec54-2a37-4516-a222-2b06719af726']) + * ``` + * @example + * ```js + * // Set visible vizs from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SetVisibleVizs, [ + * '730496d6-6903-4601-937e-2c691821af3c', + * 'd547ec54-2a37-4516-a222-2b06719af726' + * ], ContextType.Liveboard); + * ``` + * @version SDK: 1.6.0 | ThoughtSpot: ts8.nov.cl, 8.4.1.sw + */ + HostEvent["SetVisibleVizs"] = "SetPinboardVisibleVizs"; + /** + * Set a Liveboard tab as an active tab. + * @param - tabId - string of id of Tab to show + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SetActiveTab,{ + * tabId:'730496d6-6903-4601-937e-2c691821af3c' + * }) + * ``` + * @example + * ```js + * // Set active tab from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SetActiveTab, { + * tabId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.24.0 | ThoughtSpot: 9.5.0.cl, 9.5.1-sw + */ + HostEvent["SetActiveTab"] = "SetActiveTab"; + /** + * Updates the runtime filters applied on a Liveboard. The filter + * attributes passed with this event are appended to the existing runtime + * filters applied on a Liveboard. + * + * **Note**: `HostEvent.UpdateRuntimeFilters` is supported in `LiveboardEmbed` + * and `AppEmbed` only. In full application embedding, this event updates + * the runtime filters applied on the Liveboard and saved Answer objects. + * + * @param - Array of {@link RuntimeFilter} objects. Each item includes: + * - `columnName`: Name of the column to filter on. + * - `operator`: {@link RuntimeFilterOp} to apply. For more information, see + * link:https://developers.thoughtspot.com/docs/runtime-filters#rtOperator[Developer Documentation]. + * - `values`: List of operands. Some operators such as EQ and LE allow a single + * value, whereas BW and IN accept multiple values. + * + * **Note**: Updating runtime filters resets the ThoughtSpot + * object to its original state and applies new filter conditions. + * Any user changes (like drilling into a visualization) + * will be cleared, restoring the original visualization + * with the updated filters. + * + + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateRuntimeFilters, [ + * {columnName: "state",operator: RuntimeFilterOp.EQ,values: ["michigan"]}, + * {columnName: "item type",operator: RuntimeFilterOp.EQ,values: ["Jackets"]} + * ]) + * ``` + * @example + * ```js + * // Update runtime filters from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.UpdateRuntimeFilters, [ + * {columnName: "region", operator: RuntimeFilterOp.EQ, values: ["west"]}, + * {columnName: "product", operator: RuntimeFilterOp.IN, values: ["shoes", "boots"]} + * ], ContextType.Liveboard); + * ``` + * @version SDK: 1.9.0 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + * @important + */ + HostEvent["UpdateRuntimeFilters"] = "UpdateRuntimeFilters"; + /** + * Navigate to a specific page in the embedded ThoughtSpot application. + * This is the same as calling `appEmbed.navigateToPage(path, true)`. + * @param - `path` - the path to navigate to go forward or back. The path value can + * be a number; for example, `1`, `-1`. + * @example + * ```js + * appEmbed.navigateToPage(-1) + * ``` + * @version SDK: 1.12.0 | ThoughtSpot: 8.4.0.cl, 8.4.1.sw + */ + HostEvent["Navigate"] = "Navigate"; + /** + * Open the filter panel for a particular column. + * Works with Search and Liveboard embed. + * @param - { columnId: string, + * name: string, + * type: ATTRIBUTE/MEASURE, + * dataType: INT64/CHAR/DATE } + * @example + * ```js + * searchEmbed.trigger(HostEvent.OpenFilter, + * {column: { columnId: '', name: 'column name', type: 'ATTRIBUTE', dataType: 'INT64'}}) + * ``` + * @example + * ```js + * LiveboardEmbed.trigger(HostEvent.OpenFilter, + * { column: {columnId: ''}}) + * ``` + * @example + * ```js + * // Open filter from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.OpenFilter, { + * column: { columnId: '', name: 'region', type: 'ATTRIBUTE', dataType: 'CHAR'} + * }, ContextType.Search); + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl + */ + HostEvent["OpenFilter"] = "openFilter"; + /** + * Add columns to the current search query. + * @param - { columnIds: string[] } + * @example + * ```js + * searchEmbed.trigger(HostEvent.AddColumns, { columnIds: ['',''] }) + * ``` + * @example + * ```js + * // Add columns from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.AddColumns, { + * columnIds: ['col-guid-1', 'col-guid-2'] + * }, ContextType.Search); + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl + */ + HostEvent["AddColumns"] = "addColumns"; + /** + * Remove a column from the current search query. + * @param - { columnId: string } + * @example + * ```js + * searchEmbed.trigger(HostEvent.RemoveColumn, { columnId: '' }) + * ``` + * @example + * ```js + * // Remove column from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.RemoveColumn, { + * columnId: 'column-guid' + * }, ContextType.Search); + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl + */ + HostEvent["RemoveColumn"] = "removeColumn"; + /** + * Get the transient state of a Liveboard as encoded content. + * This includes unsaved and ad hoc changes such as + * Liveboard filters, runtime filters applied on visualizations on a + * Liveboard, and Liveboard layout, changes to visualizations such as + * sorting, toggling of legends, and data drill down. + * For more information, see + * link:https://developers.thoughtspot.com/docs/fetch-data-and-report-apis#transient-lb-content[Liveboard data with unsaved changes]. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.getExportRequestForCurrentPinboard).then( + * data=>console.log(data)) + * ``` + * @version SDK: 1.13.0 | ThoughtSpot: 8.5.0.cl, 8.8.1.sw + */ + HostEvent["getExportRequestForCurrentPinboard"] = "getExportRequestForCurrentPinboard"; + /** + * Trigger **Pin** action on an embedded object. + * If no parameters are defined, the pin action is triggered + * for the Answer that the user is currently on + * and a modal opens for Liveboard selection. + * To add an Answer or visualization to a Liveboard programmatically without + * requiring additional user input via the *Pin to Liveboard* modal, define + * the following parameters: + * + * @param - Includes the following keys: + * - `vizId`: GUID of the saved Answer or Spotter visualization ID to pin to a + * Liveboard. + * Optional when pinning a new chart or table generated from a Search query. + * **Required** in Spotter Embed. + * - `liveboardId`: GUID of the Liveboard to pin an Answer. If there is no Liveboard, + * specify the `newLiveboardName` parameter to create a new Liveboard. + * - `tabId`: GUID of the Liveboard tab. Adds the Answer to the Liveboard tab + * specified in the code. + * - `newVizName`: Name string for the Answer or visualization. If defined, + * this parameter adds a new visualization object or creates a copy of the + * Answer or visualization specified in `vizId`. + * Required. + * - `newLiveboardName`: Name string for the Liveboard. + * Creates a new Liveboard object with the specified name. + * - `newTabName`: Name of the tab. Adds a new tab Liveboard specified + * in the code. + * + * @example + * ```js + * const pinResponse = await appEmbed.trigger(HostEvent.Pin, { + * vizId: "123", + * newVizName: "Sales by region", + * liveboardId: "123", + * tabId: "123" + * }); + * ``` + * @example + * ```js + * const pinResponse = await appEmbed.trigger(HostEvent.Pin, { + * newVizName: "Total sales of Jackets", + * liveboardId: "123" + * }); + * ``` + * + * @example + * ```js + * const pinResponse = await searchEmbed.trigger(HostEvent.Pin, { + * newVizName: "Sales by state", + * newLiveboardName: "Sales", + * newTabName: "Products" + * }); + * ``` + * @example + * ```js + * appEmbed.trigger(HostEvent.Pin) + * ``` + * @example + * ```js + + * // You can use the Data event dispatched on each answer creation to get the vizId and use in Pin host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.Pin, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Using context parameter to specify the context type (SDK: 1.45.2+) + * // Pin from a search answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Pin, { + * vizId: "123", + * newVizName: "Sales by region", + * liveboardId: "456" + * }, ContextType.Search); + * ``` + * @example + * ```js + * // Pin from an answer context (explore modal/page) (SDK: 1.45.2+) + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Pin, { + * vizId: "789", + * newVizName: "Revenue trends", + * liveboardId: "456" + * }, ContextType.Answer); + * ``` + * @example + * ```js + * // Pin from a spotter context (SDK: 1.45.2+) + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Pin, { + * vizId: latestSpotterVizId, + * newVizName: "AI-generated insights", + * liveboardId: "456" + * }, ContextType.Spotter); + * ``` + * + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["Pin"] = "pin"; + /** + * Trigger the **Show Liveboard details** action + * on an embedded Liveboard. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.LiveboardInfo) + *``` + * @example + * ```js + * // Show liveboard info from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.LiveboardInfo, {}, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["LiveboardInfo"] = "pinboardInfo"; + /** + * Trigger the **Schedule** action on an embedded Liveboard. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Schedule) + * ``` + * @example + * ```js + * // Schedule from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Schedule, {}, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["Schedule"] = "subscription"; + /** + * Trigger the **Manage schedule** action on an embedded Liveboard + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ScheduleList) + * ``` + * @example + * ```js + * // Manage schedules from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.ScheduleList, {}, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["SchedulesList"] = "schedule-list"; + /** + * Trigger the **Export TML** action on an embedded Liveboard or + * Answer. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ExportTML) + * ``` + * @example + * ```js + * // Export TML from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.ExportTML, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Export TML from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.ExportTML, {}, ContextType.Search); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["ExportTML"] = "exportTSL"; + /** + * Trigger the **Edit TML** action on an embedded Liveboard or + * saved Answers in the full application embedding. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.EditTML) + * ``` + * @example + * ```js + * // Edit TML from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.EditTML, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Edit TML from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.EditTML, {}, ContextType.Search); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["EditTML"] = "editTSL"; + /** + * Trigger the **Update TML** action on an embedded Liveboard. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateTML) + * ``` + * @example + * ```js + * // Update TML from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.UpdateTML, {}, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["UpdateTML"] = "updateTSL"; + /** + * Trigger the **Download PDF** action on an embedded Liveboard, + * visualization or Answer. + * + * @param - `vizId` refers to the Answer ID in Spotter embed and is required in Spotter embed. + * + * **NOTE**: The **Download** > **PDF** action is available on + * visualizations and Answers if the data is in tabular format. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.DownloadAsPdf) + * ``` + * @example + * ```js + + * // You can use the Data event dispatched on each answer creation to get the vizId and use in DownloadAsPdf host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.DownloadAsPdf, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Download as PDF from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.DownloadAsPdf, {}, ContextType.Search); + * ``` + * @example + * ```js + * // Download as PDF from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.DownloadAsPdf, {}, ContextType.Liveboard); + * ``` + * + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["DownloadAsPdf"] = "downloadAsPdf"; + /** + * Trigger the **Download Liveboard as Continuous PDF** action on an + * embedded Liveboard. + * + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.DownloadLiveboardAsContinuousPDF) + * ``` + * + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + HostEvent["DownloadLiveboardAsContinuousPDF"] = "downloadLiveboardAsContinuousPDF"; + /** + * Trigger the **AI Highlights** action on an embedded Liveboard + * + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.AIHighlights) + * ``` + * @version SDK: 1.44.0 | ThoughtSpot: 10.15.0.cl + */ + HostEvent["AIHighlights"] = "AIHighlights"; + /** + * Trigger the **Make a copy** action on a Liveboard, + * visualization, or Answer page. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.MakeACopy) + * ``` + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.MakeACopy, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * @example + * ```js + * vizEmbed.trigger(HostEvent.MakeACopy) + * ``` + * @example + * ```js + * searchEmbed.trigger(HostEvent.MakeACopy) + * ``` + * @example + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in MakeACopy host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.MakeACopy, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Make a copy from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.MakeACopy, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Make a copy from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.MakeACopy, {}, ContextType.Search); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["MakeACopy"] = "makeACopy"; + /** + * Trigger the **Delete** action for a Liveboard. + * @example + * ```js + * appEmbed.trigger(HostEvent.Remove) + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Remove) + * ``` + * @version SDK: 1.37.0 | ThoughtSpot: 10.8.0.cl, 10.10.0.sw + */ + HostEvent["Remove"] = "delete"; + /** + * Trigger the **Explore** action on a visualization. + * @param - an object with `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Explore, {vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * @example + * ```js + * // Explore from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Explore, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["Explore"] = "explore"; + /** + * Trigger the **Create alert** action on a KPI chart + * in a Liveboard or saved Answer. + * @param - an object with `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.CreateMonitor, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }) + * ``` + * @example + * ```js + * searchEmbed.trigger(HostEvent.CreateMonitor) + * ``` + * @example + * ```js + * // Create monitor from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.CreateMonitor, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Create monitor from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.CreateMonitor, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["CreateMonitor"] = "createMonitor"; + /** + * Trigger the **Manage alerts** action on a KPI chart + * in a visualization or saved Answer. + * @param - an object with `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ManageMonitor, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }) + * ``` + * @example + * ```js + * searchEmbed.trigger(HostEvent.ManageMonitor) + * ``` + * @example + * ```js + * vizEmbed.trigger(HostEvent.ManageMonitor) + * ``` + * @example + * ```js + * // Manage monitor from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.ManageMonitor, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Manage monitor from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.ManageMonitor, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["ManageMonitor"] = "manageMonitor"; + /** + * Trigger the **Edit** action on a Liveboard or a visualization + * on a Liveboard. + * + * This event is not supported in visualization embed and search embed. + * @param - Object parameter. Includes the following keys: + * - `vizId`: To trigger the action for a specific visualization in Liveboard embed, + * pass in `vizId` as a key. In Spotter embed, `vizId` refers to the Answer ID and + * is **required**. + * + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Edit) + * ``` + * ```js + * liveboardEmbed.trigger(HostEvent.Edit, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * @example + * ```js + * spotterEmbed.trigger(HostEvent.Edit); + * ``` + * @example + * ```js + * // Using context parameter to edit liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Edit, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Edit from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Edit, {}, ContextType.Search); + * ``` + * * @example + * ```js + * // Edit from spotter context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Edit, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Spotter); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["Edit"] = "edit"; + /** + * Trigger the **Copy link** action on a Liveboard or visualization + * @param - object - to trigger the action for a + * specific visualization in Liveboard embed, pass in `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.CopyLink) + * ``` + * ```js + * liveboardEmbed.trigger(HostEvent.CopyLink, {vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.CopyLink) + * ``` + * @example + * ```js + * // Copy link from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.CopyLink, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Copy link from liveboard visualization context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.CopyLink, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Copy link from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.CopyLink, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Copy link from liveboard visualization context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.CopyLink, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["CopyLink"] = "embedDocument"; + /** + * Trigger the **Present** action on a Liveboard or visualization + * @param - object - to trigger the action for a specific visualization + * in Liveboard embed, pass in `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Present) + * ``` + * ```js + * liveboardEmbed.trigger(HostEvent.Present, {vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.Present) + * ``` + * @example + * ```js + * // Present from liveboard visualization context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Present, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Present from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Present, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Present from liveboard visualization context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Present, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Present from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Present, {}, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["Present"] = "present"; + /** + * Get TML for the current search. + * @example + * ```js + * searchEmbed.trigger(HostEvent.GetTML).then((tml) => { + * console.log( + * tml.answer.search_query // TML representation of the search query + * ); + * }) + * ``` + * @example + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in GetTML host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.GetTML, { + * vizId: latestSpotterVizId + * }).then((tml) => { + * console.log( + * tml.answer.search_query // TML representation of the search query + * ); + * }) + * ``` + * @example + * ```js + * // Get TML from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.GetTML, {}, ContextType.Search).then((tml) => { + * console.log(tml.answer.search_query); + * }); + * ``` + * @example + * ```js + * // Get TML from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.GetTML, {}, ContextType.Search).then((tml) => { + * console.log(tml.answer); + * }); + * ``` + * @version SDK: 1.18.0 | ThoughtSpot: 8.10.0.cl, 9.0.1.sw + * @important + */ + HostEvent["GetTML"] = "getTML"; + /** + * Trigger the **Show underlying data** action on a + * chart or table. + * + * @param - an object with vizId as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ShowUnderlyingData, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.ShowUnderlyingData) + * ``` + * ```js + * searchEmbed.trigger(HostEvent.ShowUnderlyingData) + * ``` + * @example + * ```js + * // Show underlying data from liveboard visualization context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.ShowUnderlyingData, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Show underlying data from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.ShowUnderlyingData, {}, ContextType.Search); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["ShowUnderlyingData"] = "showUnderlyingData"; + /** + * Trigger the **Delete** action for a visualization + * in an embedded Liveboard, or a chart or table + * generated from Search. + * @param - Liveboard embed takes an object with `vizId` as a key. + * Can be left empty if embedding Search or visualization. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Delete, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * searchEmbed.trigger(HostEvent.Delete) + * ``` + * @example + * ```js + * // Delete from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Delete, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Delete from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Delete, {}, ContextType.Search); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["Delete"] = "onDeleteAnswer"; + /** + * Trigger the **SpotIQ analyze** action on a + * chart or table. + * @param - Liveboard embed takes `vizId` as a + * key. Can be left undefined when embedding Search or + * visualization. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SpotIQAnalyze, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.SpotIQAnalyze) + * ``` + * ```js + * searchEmbed.trigger(HostEvent.SpotIQAnalyze) + * ``` + * @example + * ```js + * // SpotIQ analyze from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.SpotIQAnalyze, { vizId: '730496d6-6903-4601-937e-2c691821af3c' }, ContextType.Search); + * ``` + * @example + * ```js + * // SpotIQ analyze from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SpotIQAnalyze, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["SpotIQAnalyze"] = "spotIQAnalyze"; + /** + * Trigger the **Download** action on charts in + * the embedded view. + * Use {@link HostEvent.DownloadAsPng} instead. + * + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + * + * @deprecated from SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl ,9.4.1.sw + * @param - `vizId` refers to the Visualization ID in Spotter embed and is required in Spotter embed. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Download, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * embed.trigger(HostEvent.Download) + * ``` + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in Download host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.Download, { vizId: latestSpotterVizId }); + * ``` + */ + HostEvent["Download"] = "downloadAsPng"; + /** + * Trigger the **Download** > **PNG** action on + * charts in the embedded view. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.DownloadAsPng, + * {vizId:'730496d6-6903-4601-937e-2c691821af3c'}) + * + * vizEmbed.trigger(HostEvent.DownloadAsPng) + * + * searchEmbed.trigger(HostEvent.DownloadAsPng) + * + * // You can use the Data event dispatched on each answer creation to get the vizId and use in DownloadAsPng host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.DownloadAsPng, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Download as PNG from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.DownloadAsPng, {}, ContextType.Search); + * ``` + * + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.4.1.sw + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + HostEvent["DownloadAsPng"] = "downloadAsPng"; + /** + * Trigger the **Download** > **CSV** action on tables in + * the embedded view. + * @param - `vizId` refers to the Visualization ID in Spotter embed and is required in Spotter embed. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.DownloadAsCsv, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.DownloadAsCsv) + * ``` + * ```js + * searchEmbed.trigger(HostEvent.DownloadAsCsv) + * ``` + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in DownloadAsCsv host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.DownloadAsCsv, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Download as CSV from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.DownloadAsCsv, {}, ContextType.Search); + * ``` + * @example + * ```js + * // Download as CSV from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.DownloadAsCsv, {}, ContextType.Search); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["DownloadAsCsv"] = "downloadAsCSV"; + /** + * Trigger the **Download** > **XLSX** action on tables + * in the embedded view. + * @param - `vizId` refers to the Visualization ID in Spotter embed and is required in Spotter embed. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.DownloadAsXlsx, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.DownloadAsXlsx) + * ``` + * ```js + * searchEmbed.trigger(HostEvent.DownloadAsXlsx) + * ``` + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in DownloadAsXlsx host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.DownloadAsXlsx, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Download as XLSX from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.DownloadAsXlsx, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Download as XLSX from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.DownloadAsXlsx, {}, ContextType.Search); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["DownloadAsXlsx"] = "downloadAsXLSX"; + /** + * Trigger the **Share** action on an embedded + * Liveboard or Answer. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Share) + * ``` + * ```js + * searchEmbed.trigger(HostEvent.Share) + * ``` + * @example + * ```js + * // Share from Liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Share, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Share from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Share, {}, ContextType.Search); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["Share"] = "share"; + /** + * Trigger the **Save** action on a Liveboard, Answer, or Spotter. + * Saves the changes. + * + * @param - `vizId` refers to the Spotter Visualization Id used in Spotter embed. + * It is required and can be retrieved from the data embed event. + * + * @example + * ```js + * // Save changes in a Liveboard + * liveboardEmbed.trigger(HostEvent.Save) + * ``` + * + * ```js + * // Save the current Answer in Search embed + * searchEmbed.trigger(HostEvent.Save) + * ``` + * + * ```js + * // Save a Visualization in Spotter (requires vizId) + * spotterEmbed.trigger(HostEvent.Save, { + * vizId: "730496d6-6903-4601-937e-2c691821af3c" + * }) + * ``` + * + * ```js + * // How to get the vizId in Spotter? + * + * // You can use the Data event dispatched on each answer creation to get the vizId. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.Save, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Save from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Save, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Save from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.Save, {}, ContextType.Search); + * ``` + * + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["Save"] = "save"; + /** + * Trigger the **Sync to Sheets** action on an embedded visualization or Answer + * Sends data from an Answer or Liveboard visualization to a Google sheet. + * @param - an object with `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SyncToSheets, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.SyncToSheets) + * ``` + * @example + * ```js + * // Sync to sheets from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.SyncToSheets, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Sync to sheets from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SyncToSheets, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["SyncToSheets"] = "sync-to-sheets"; + /** + * Trigger the **Sync to Other Apps** action on an embedded visualization or Answer + * Sends data from an Answer or Liveboard visualization to third-party apps such + * as Slack, Salesforce, Microsoft Teams, ServiceNow and so on. + * @param - an object with vizId as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SyncToOtherApps, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.SyncToOtherApps) + * ``` + * @example + * ```js + * // Sync to other apps from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.SyncToOtherApps, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Sync to other apps from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SyncToOtherApps, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["SyncToOtherApps"] = "sync-to-other-apps"; + /** + * Trigger the **Manage pipelines** action on an embedded + * visualization or Answer. + * Allows users to manage ThoughtSpot Sync pipelines. + * @param - an object with `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ManagePipelines, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.ManagePipelines) + * ``` + * @example + * ```js + * // Manage pipelines from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.ManagePipelines, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Manage pipelines from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.ManagePipelines, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["ManagePipelines"] = "manage-pipeline"; + /** + * Reset search operation on the Search or Answer page. + * @example + * ```js + * searchEmbed.trigger(HostEvent.ResetSearch) + * ``` + * ```js + * appEmbed.trigger(HostEvent.ResetSearch) + * ``` + * @example + * ```js + * // Reset search from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.ResetSearch, {}, ContextType.Search); + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.0.1.sw + */ + HostEvent["ResetSearch"] = "resetSearch"; + /** + * Get details of filters applied on the Liveboard. + * Returns arrays containing Liveboard filter and runtime filter elements. + * @example + * ```js + * const data = await liveboardEmbed.trigger(HostEvent.GetFilters); + * console.log('data', data); + * ``` + * @example + * ```js + * // Get filters from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * const data = await liveboardEmbed.trigger(HostEvent.GetFilters, {}, ContextType.Liveboard); + * console.log('filters', data); + * ``` + * @version SDK: 1.23.0 | ThoughtSpot: 9.4.0.cl + */ + HostEvent["GetFilters"] = "getFilters"; + /** + * Update one or several filters applied on a Liveboard. + * @param - Includes the following keys: + * - `filter`: A single filter object containing column name, filter operator, and + * values. + * - `filters`: Multiple filter objects with column name, filter operator, + * and values for each. + * + * Each filter object must include the following attributes: + * + * `column` - Name of the column to filter on. + * + * `oper` - Filter operator, for example, EQ, IN, CONTAINS. + * For information about the supported filter operators, + * see link:https://developers.thoughtspot.com/docs/runtime-filters#rtOperator[Developer Documentation]. + * + * `values` - An array of one or several values. The value definition on the + * data type you choose to filter on. For a complete list of supported data types, + * see + * link:https://developers.thoughtspot.com/docs/runtime-filters#_supported_data_types[Supported + * data types]. + * + * `type` - To update filters for date time, specify the date format type. + * For more information and examples, see link:https://developers.thoughtspot.com/docs/embed-liveboard#_date_filters[Date filters]. + * @example + * ```js + * + * liveboardEmbed.trigger(HostEvent.UpdateFilters, { + * filter: { + * column: "item type", + * oper: "IN", + * values: ["bags","shirts"] + * } + * }); + * ``` + * @example + * ```js + * + * liveboardEmbed.trigger(HostEvent.UpdateFilters, { + * filter: { + * column: "date", + * oper: "EQ", + * values: ["JULY","2023"], + * type: "MONTH_YEAR" + * } + * }); + * ``` + * @example + * + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateFilters, { + * filters: [{ + * column: "Item Type", + * oper: 'IN', + * values: ["bags","shirts"] + * }, + * { + * column: "Region", + * oper: 'IN', + * values: ["West","Midwest"] + * }, + * { + * column: "Date", + * oper: 'EQ', + * values: ["2023-07-31"], + * type: "EXACT_DATE" + * }] + * }); + * ``` + * If there are multiple columns with the same name, consider + * using `WORKSHEET_NAME::COLUMN_NAME` format. + * + * @example + * + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateFilters, { + * filters: [{ + * column: "(Sample) Retail - Apparel::city", + * oper: 'IN', + * values: ["atlanta"] + * }, + * { + * column: "(Sample) Retail - Apparel::Region", + * oper: 'IN', + * values: ["West","Midwest"] + * }] + * }); + * ``` + * @example + * ```js + * // Update filters from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.UpdateFilters, { + * filter: { + * column: "item type", + * oper: "IN", + * values: ["shoes", "boots"] + * } + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.23.0 | ThoughtSpot: 9.4.0.cl + */ + HostEvent["UpdateFilters"] = "updateFilters"; + /** + * Get tab details for the current Liveboard. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.GetTabs).then((tabDetails) => { + * console.log( + * tabDetails // TabDetails of current Liveboard + * ); + * }) + * ``` + * @example + * ```js + * // Get tabs from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.GetTabs, {}, ContextType.Liveboard).then((tabDetails) => { + * console.log('tabs', tabDetails); + * }); + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl + */ + HostEvent["GetTabs"] = "getTabs"; + /** + * Set the visible tabs on a Liveboard. + * @param - an array of ids of tabs to show, the IDs not passed + * will be hidden. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SetVisibleTabs, [ + * '430496d6-6903-4601-937e-2c691821af3c', + * 'f547ec54-2a37-4516-a222-2b06719af726']) + * ``` + * @example + * ```js + * // Set visible tabs from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SetVisibleTabs, [ + * '430496d6-6903-4601-937e-2c691821af3c', + * 'f547ec54-2a37-4516-a222-2b06719af726' + * ], ContextType.Liveboard); + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + */ + HostEvent["SetVisibleTabs"] = "SetPinboardVisibleTabs"; + /** + * Set the hidden tabs on a Liveboard. + * @param - an array of the IDs of the tabs to hide. + * The IDs not passed will be shown. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SetHiddenTabs, [ + * '630496d6-6903-4601-937e-2c691821af3c', + * 'i547ec54-2a37-4516-a222-2b06719af726']) + * ``` + * @example + * ```js + * // Set hidden tabs from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SetHiddenTabs, [ + * '630496d6-6903-4601-937e-2c691821af3c', + * 'i547ec54-2a37-4516-a222-2b06719af726' + * ], ContextType.Liveboard); + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + */ + HostEvent["SetHiddenTabs"] = "SetPinboardHiddenTabs"; + /** + * Get the Answer session for a Search or + * Liveboard visualization. + * + * Note: This event is not typically used directly. Instead, use the + * `getAnswerService()` method on the embed instance to get an AnswerService + * object that provides a more convenient interface for working with answers. + * + * @example + * ```js + * // Preferred way to get an AnswerService + * const service = await embed.getAnswerService(); + * + * // Alternative direct usage (not recommended) + * const {session} = await embed.trigger( + * HostEvent.GetAnswerSession, { + * vizId: '123', // For Liveboard Visualization. + * }) + * ``` + * @example + * ```js + * // Preferred way to get an AnswerService + * const service = await embed.getAnswerService(); + * + * // Alternative direct usage (not recommended) + * const {session} = await embed.trigger( HostEvent.GetAnswerSession ) + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.10.0.cl, 10.1.0.sw + */ + HostEvent["GetAnswerSession"] = "getAnswerSession"; + /** + * Trigger the *Ask Sage* action for visualizations + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.AskSage, + * {vizId:'730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * @version SDK: 1.29.0 | ThoughtSpot Cloud: 9.12.0.cl + */ + HostEvent["AskSage"] = "AskSage"; + /** + * Trigger cross filter update action on a Liveboard. + * + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateCrossFilter, { + * vizId: 'b535c760-8bbe-4e6f-bb26-af56b4129a1e', + * conditions: [ + * { columnName: 'Category', values: ['mfgr#12','mfgr#14'] }, + * { columnName: 'color', values: ['mint','hot'] }, + * ], + * }); + * ``` + * @version SDK: 1.29.0 | ThoughtSpot Cloud: 10.0.0.cl, 10.1.0.sw + */ + HostEvent["UpdateCrossFilter"] = "UpdateCrossFilter"; + /** + * Trigger reset action for a personalized Liveboard view. + * This event is deprecated. Use {@link HostEvent.ResetLiveboardPersonalizedView} instead. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ResetLiveboardPersonalisedView); + * ``` + * @version SDK: 1.29.0 | ThoughtSpot Cloud: 10.1.0.cl, 10.1.0.sw + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + HostEvent["ResetLiveboardPersonalisedView"] = "ResetLiveboardPersonalisedView"; + /** + * Trigger reset action for a personalized Liveboard view. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ResetLiveboardPersonalizedView); + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + HostEvent["ResetLiveboardPersonalizedView"] = "ResetLiveboardPersonalisedView"; + /** + * Triggers an action to update Parameter values on embedded + * Answers, Liveboard, and Spotter answer in Edit mode. + * @param - Includes the following keys for each item: + * - `name`: Name of the parameter. + * - `value`: The value to set for the parameter. + * - `isVisibleToUser`: Optional. To control the visibility of the parameter chip. + * + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateParameters, [{ + * name: "Integer Range Param", + * value: 10, + * isVisibleToUser: false + * }]) + * ``` + * @example + * ```js + * // Update parameters from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.UpdateParameters, [{ + * name: "Region Param", + * value: "West", + * isVisibleToUser: true + * }], ContextType.Liveboard); + * ``` + * @version SDK: 1.29.0 | ThoughtSpot: 10.1.0.cl, 10.1.0.sw + */ + HostEvent["UpdateParameters"] = "UpdateParameters"; + /** + * Triggers GetParameters to fetch the runtime Parameters. + * @param - `vizId` refers to the Answer ID in Spotter embed and is required in Spotter embed. + * ```js + * liveboardEmbed.trigger(HostEvent.GetParameters).then((parameter) => { + * console.log('parameters', parameter); + * }); + * ``` + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in GetParameters host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.GetParameters, { vizId: latestSpotterVizId }); + *``` + * @example + * ```js + * // Get parameters from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.GetParameters, {}, + * ContextType.Liveboard).then((parameters) => { + * console.log('parameters', parameters); + * }); + * ``` + * @version SDK: 1.29.0 | ThoughtSpot: 10.1.0.cl, 10.1.0.sw + */ + HostEvent["GetParameters"] = "GetParameters"; + /** + * Triggers an event to update a personalized view of a Liveboard. + * This event is deprecated. Use {@link HostEvent.UpdatePersonalizedView} instead. + * ```js + * liveboardEmbed.trigger(HostEvent.UpdatePersonalisedView, {viewId: '1234'}) + * ``` + * @example + * ```js + * // Update personalized view from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.UpdatePersonalisedView, { + * viewId: '1234' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.36.0 | ThoughtSpot: 10.6.0.cl + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + HostEvent["UpdatePersonalisedView"] = "UpdatePersonalisedView"; + /** + * Triggers an event to update a personalized view of a Liveboard. + * ```js + * liveboardEmbed.trigger(HostEvent.UpdatePersonalisedView, {viewId: '1234'}) + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + HostEvent["UpdatePersonalizedView"] = "UpdatePersonalisedView"; + /** + * Triggers selection of a specific Personalized View on a + * Liveboard without reloading the embed. Pass either a + * `viewId` (GUID) or `viewName`. If both are provided, `viewId` takes precedence. + * If neither is provided, the Liveboard resets to the original/default view. + * When a `viewName` is provided and multiple views share + * the same name, the first match is selected. + * @example + * ```js + * liveboardEmbed.trigger( + * HostEvent.SelectPersonalizedView, + * { viewId: '2a021a12-1aed-425d-984b-141ee916ce72' }, + * ) + * ``` + * @example + * ```js + * // Select by name + * liveboardEmbed.trigger( + * HostEvent.SelectPersonalizedView, + * { viewName: 'Dr Smith Cardiology' }, + * ) + * ``` + * @example + * ```js + * // Reset to default view + * liveboardEmbed.trigger( + * HostEvent.SelectPersonalizedView, + * {}, + * ) + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + HostEvent["SelectPersonalizedView"] = "SelectPersonalisedView"; + /** + * @hidden + * Notify when info call is completed successfully + * ```js + * liveboardEmbed.trigger(HostEvent.InfoSuccess, data); + * ``` + * @version SDK: 1.36.0 | ThoughtSpot: 10.6.0.cl + */ + HostEvent["InfoSuccess"] = "InfoSuccess"; + /** + * Trigger the save action for an Answer. + * To programmatically save an answer without opening the + * *Describe your Answer* modal, define the `name` and `description` + * properties. + * If no parameters are specified, the save action is + * triggered with a modal to prompt users to + * add a name and description for the Answer. + * @param - Includes the following keys: + * - `vizId`: Refers to the Answer ID in Spotter embed and is **required** in Spotter + * embed. + * - `name`: Optional. Name string for the Answer. + * - `description`: Optional. Description text for the Answer. + * @example + * ```js + * const saveAnswerResponse = await searchEmbed.trigger(HostEvent.SaveAnswer, { + * name: "Sales by states", + * description: "Total sales by states in MidWest" + * }); + * ``` + * @example + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in SaveAnswer host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.SaveAnswer, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Using context parameter to save answer from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * const saveAnswerResponse = await appEmbed.trigger(HostEvent.SaveAnswer, { + * name: "Regional sales analysis", + * description: "Sales breakdown by region" + * }, ContextType.Search); + * ``` + * @example + * ```js + * // Save answer from answer context (explore modal) + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * const saveAnswerResponse = await appEmbed.trigger(HostEvent.SaveAnswer, { + * name: "Modified analysis", + * description: "Updated from explore view" + * }, ContextType.Answer); + * ``` + * @example + * ```js + * // Save answer from spotter context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * const saveAnswerResponse = await appEmbed.trigger(HostEvent.SaveAnswer, { + * vizId: latestSpotterVizId, + * name: "AI insights", + * description: "Generated from Spotter" + * }, ContextType.Spotter); + * ``` + * @version SDK: 1.36.0 | ThoughtSpot: 10.6.0.cl + */ + HostEvent["SaveAnswer"] = "saveAnswer"; + /** + * EmbedApi + * @hidden + */ + HostEvent["UIPassthrough"] = "UiPassthrough"; + /** + * Triggers the table visualization re-render with the updated data. + * Includes the following properties: + * @param - `columnDataLite` - an array of object containing the + * data value modifications retrieved from the `EmbedEvent.TableVizRendered` + * payload.For example, { columnDataLite: []}`. + * + * @example + * ```js + * searchEmbed.on(EmbedEvent.TableVizRendered, (payload) => { + * console.log(payload); + * const columnDataLite = payload.data.data.columnDataLite; + * columnDataLite[0].dataValue[0]="new fob"; + * console.log('>>> new Data', columnDataLite); + * searchEmbed.trigger(HostEvent.TransformTableVizData, columnDataLite); + * }) + * ``` + * @version SDK: 1.37.0 | ThoughtSpot: 10.8.0.cl + */ + HostEvent["TransformTableVizData"] = "TransformTableVizData"; + /** + * Triggers a search operation with the search tokens specified in + * the search query string in spotter embed. + * @param - Includes the following keys: + * - `query`: Text string in Natural Language format. + * - `executeSearch`: Boolean to execute search and update search query. + * @example + * ```js + * spotterEmbed.trigger(HostEvent.SpotterSearch, { + * query: 'revenue per year', + * executeSearch: true, + * }) + * ``` + * @example + * ```js + * // Spotter search from spotter context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * spotterEmbed.trigger(HostEvent.SpotterSearch, { + * query: 'sales by region', + * executeSearch: true + * }, ContextType.Spotter); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HostEvent["SpotterSearch"] = "SpotterSearch"; + /** + * Edits the last prompt in spotter embed. + * @param - `query`: Text string + * @example + * ```js + * spotterEmbed.trigger(HostEvent.EditLastPrompt, "revenue per year"); + * ``` + * @example + * ```js + * // Edit last prompt from spotter context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * spotterEmbed.trigger(HostEvent.EditLastPrompt, "sales by region", ContextType.Spotter); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HostEvent["EditLastPrompt"] = "EditLastPrompt"; + /** + * Opens the data source preview modal in Spotter Embed. + * @example + * ```js + * spotterEmbed.trigger(HostEvent.PreviewSpotterData); + * ``` + * @example + * ```js + * // Preview spotter data from spotter context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * spotterEmbed.trigger(HostEvent.PreviewSpotterData, {}, ContextType.Spotter); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HostEvent["PreviewSpotterData"] = "PreviewSpotterData"; + /** + * Opens the Add to Coaching modal from a visualization in Spotter Embed. + * @param - `vizId ` refers to the Visualization ID in Spotter embed and is required. + * @example + * ```js + * spotterEmbed.trigger(HostEvent.AddToCoaching, { vizId: '730496d6-6903-4601-937e-2c691821af3c' }); + * + * ``` + * @version SDK: 1.45.0 | ThoughtSpot: 26.2.0.cl + */ + HostEvent["AddToCoaching"] = "addToCoaching"; + /** + * Opens the data model instructions modal in Spotter Embed. + * @example + * ```js + * spotterEmbed.trigger(HostEvent.DataModelInstructions); + * ``` + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + */ + HostEvent["DataModelInstructions"] = "DataModelInstructions"; + /** + * Resets the Spotter Embed Conversation. + * @example + * ```js + * spotterEmbed.trigger(HostEvent.ResetSpotterConversation); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HostEvent["ResetSpotterConversation"] = "ResetSpotterConversation"; + /** + * Deletes the last prompt in spotter embed. + * @example + * ```js + * spotterEmbed.trigger(HostEvent.DeleteLastPrompt); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HostEvent["DeleteLastPrompt"] = "DeleteLastPrompt"; + /** + * Toggle the visualization to chart or table view. + * @param - `vizId ` refers to the Visualization ID in Spotter embed and is required. + * @example + * ```js + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.AnswerChartSwitcher, { vizId: latestSpotterVizId }); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HostEvent["AnswerChartSwitcher"] = "answerChartSwitcher"; + /** + * @hidden + * Trigger exit from presentation mode when user exits fullscreen. + * This is automatically triggered by the SDK when fullscreen mode is exited. + * ```js + * liveboardEmbed.trigger(HostEvent.ExitPresentMode); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HostEvent["ExitPresentMode"] = "exitPresentMode"; + /** + * Triggers the full height lazy load data. + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.RequestVisibleEmbedCoordinates, (payload) => { + * console.log(payload); + * }); + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + * + * @hidden + */ + HostEvent["VisibleEmbedCoordinates"] = "visibleEmbedCoordinates"; + /** + * Trigger the *Spotter* action for visualizations present on the liveboard's vizzes. + * @param - `vizId` refers to the Visualization ID in Spotter embed and is required. + * @example + * ```js + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.AskSpotter, { vizId: latestSpotterVizId }); + * ``` + * @version SDK: 1.41.0 | ThoughtSpot: 10.12.0.cl + */ + HostEvent["AskSpotter"] = "AskSpotter"; + /** + * @hidden + * Triggers the update of the embed params. + * + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateEmbedParams, viewConfig); + * ``` + */ + HostEvent["UpdateEmbedParams"] = "updateEmbedParams"; + /** + * Triggered when the embed needs to be destroyed. This is used to clean up any embed-related resources internally. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.DestroyEmbed); + * ``` + * @version SDK: 1.41.0 | ThoughtSpot: 10.12.0.cl + */ + HostEvent["DestroyEmbed"] = "EmbedDestroyed"; + /** + * Triggers a new conversation in Spotter embed. + * + * This feature is available only when chat history is enabled on your ThoughtSpot + * instance. Contact your admin or ThoughtSpot Support to enable chat history on your + * instance. + * + * @example + * ```js + * spotterEmbed.trigger(HostEvent.StartNewSpotterConversation); + * ``` + * @version SDK: 1.45.0 | ThoughtSpot: 26.2.0.cl + */ + HostEvent["StartNewSpotterConversation"] = "StartNewSpotterConversation"; + /** + * @hidden + * Get the current context of the embedded page. + * + * @example + * ```js + * const context = await liveboardEmbed.trigger(HostEvent.GetPageContext); + * ``` + * @version SDK: 1.45.0 | ThoughtSpot: 26.2.0.cl + */ + HostEvent["GetPageContext"] = "GetPageContext"; + /** + * Trigger the **Send Test Email** action in the Liveboard schedule modal. + * Sends a test schedule email to self or all recipients. + * Requires `isSendNowLiveboardSchedulingEnabled` to be enabled. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SendTestScheduleEmail, { + * sendToSelf: true, + * }) + * ``` + * @example + * ```js + * // Send to all recipients + * liveboardEmbed.trigger(HostEvent.SendTestScheduleEmail, { + * sendToSelf: false, + * }) + * ``` + * @version SDK: 1.48.0 | ThoughtSpot Cloud: 26.5.0.cl + */ + HostEvent["SendTestScheduleEmail"] = "sendTestScheduleEmail"; + /** + * Sends a user message (prompt) to the SpotterViz panel programmatically. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @param query - the prompt text to send. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SpotterVizSendUserMessage, { + * query: 'Show me revenue by region', + * }); + * ``` + */ + HostEvent["SpotterVizSendUserMessage"] = "SpotterVizSendUserMessage"; + /** + * Initializes a new SpotterViz conversation. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.InitSpotterVizConversation); + * ``` + */ + HostEvent["InitSpotterVizConversation"] = "InitSpotterVizConversation"; + /** + * Clears browser cache and fetches new data for liveboard ChartViz Containers. + * Requires `enableLiveboardDataCache` to be enabled. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.RefreshLiveboardBrowserCache); + * ``` + * @version SDK: 1.49.0 | ThoughtSpot Cloud: 26.6.0.cl + */ + HostEvent["RefreshLiveboardBrowserCache"] = "refreshLiveboardBrowserCache"; +})(HostEvent || (HostEvent = {})); +/** + * The different visual modes that the data sources panel within + * search could appear in, such as hidden, collapsed, or expanded. + */ +var DataSourceVisualMode; +(function (DataSourceVisualMode) { + /** + * The data source panel is hidden. + */ + DataSourceVisualMode["Hidden"] = "hide"; + /** + * The data source panel is collapsed, but the user can manually expand it. + */ + DataSourceVisualMode["Collapsed"] = "collapse"; + /** + * The data source panel is expanded, but the user can manually collapse it. + */ + DataSourceVisualMode["Expanded"] = "expand"; +})(DataSourceVisualMode || (DataSourceVisualMode = {})); +/** + * The query params passed down to the embedded ThoughtSpot app + * containing configuration and/or visual information. + */ +var Param; +(function (Param) { + Param["Tsmcp"] = "tsmcp"; + Param["EmbedApp"] = "embedApp"; + Param["DataSources"] = "dataSources"; + Param["DataSourceMode"] = "dataSourceMode"; + Param["DisableActions"] = "disableAction"; + Param["DisableActionReason"] = "disableHint"; + Param["ForceTable"] = "forceTable"; + Param["preventLiveboardFilterRemoval"] = "preventPinboardFilterRemoval"; + Param["SearchQuery"] = "searchQuery"; + Param["HideActions"] = "hideAction"; + Param["HideObjects"] = "hideObjects"; + Param["HostAppUrl"] = "hostAppUrl"; + Param["EnableVizTransformations"] = "enableVizTransform"; + Param["EnableSearchAssist"] = "enableSearchAssist"; + Param["EnableConnectionNewExperience"] = "newConnectionsExperience"; + Param["EnablePendoHelp"] = "enablePendoHelp"; + Param["HideResult"] = "hideResult"; + Param["UseLastSelectedDataSource"] = "useLastSelectedSources"; + Param["Tag"] = "tag"; + Param["HideTagFilterChips"] = "hideTagFilterChips"; + Param["AutoLogin"] = "autoLogin"; + Param["searchTokenString"] = "searchTokenString"; + Param["executeSearch"] = "executeSearch"; + Param["fullHeight"] = "isFullHeightPinboard"; + Param["livedBoardEmbed"] = "isLiveboardEmbed"; + Param["searchEmbed"] = "isSearchEmbed"; + Param["vizEmbed"] = "isVizEmbed"; + Param["StringIDsUrl"] = "overrideStringIDsUrl"; + Param["Version"] = "sdkVersion"; + Param["ViewPortHeight"] = "viewPortHeight"; + Param["ViewPortWidth"] = "viewPortWidth"; + Param["VisibleActions"] = "visibleAction"; + Param["DisableLoginRedirect"] = "disableLoginRedirect"; + Param["visibleVizs"] = "pinboardVisibleVizs"; + Param["LiveboardV2Enabled"] = "isPinboardV2Enabled"; + Param["DataPanelV2Enabled"] = "enableDataPanelV2"; + Param["ShowAlerts"] = "showAlerts"; + Param["Locale"] = "locale"; + Param["CustomStyle"] = "customStyle"; + Param["ForceSAMLAutoRedirect"] = "forceSAMLAutoRedirect"; + // eslint-disable-next-line @typescript-eslint/no-shadow + Param["AuthType"] = "authType"; + Param["IconSpriteUrl"] = "iconSprite"; + Param["cookieless"] = "cookieless"; + // Deprecated: `isContextMenuEnabledOnLeftClick` + // Introduced: `contextMenuEnabledOnWhichClick` with values: 'left', + // 'right', or 'both'. This update only affects ThoughtSpot URL parameters + // and does not impact existing workflows or use cases. Added support for + // 'both' clicks in `contextMenuTrigger` configuration. + Param["ContextMenuTrigger"] = "contextMenuEnabledOnWhichClick"; + Param["LinkOverride"] = "linkOverride"; + Param["EnableLinkOverridesV2"] = "enableLinkOverridesV2"; + Param["blockNonEmbedFullAppAccess"] = "blockNonEmbedFullAppAccess"; + Param["ShowInsertToSlide"] = "insertInToSlide"; + Param["PrimaryNavHidden"] = "primaryNavHidden"; + Param["HideProfleAndHelp"] = "profileAndHelpInNavBarHidden"; + Param["NavigationVersion"] = "navigationVersion"; + Param["HideHamburger"] = "hideHamburger"; + Param["HideObjectSearch"] = "hideObjectSearch"; + Param["HideNotification"] = "hideNotification"; + Param["HideApplicationSwitcher"] = "applicationSwitcherHidden"; + Param["HideOrgSwitcher"] = "orgSwitcherHidden"; + Param["HideWorksheetSelector"] = "hideWorksheetSelector"; + Param["DisableWorksheetChange"] = "disableWorksheetChange"; + Param["HideSourceSelection"] = "hideSourceSelection"; + Param["DisableSourceSelection"] = "disableSourceSelection"; + Param["HideEurekaResults"] = "hideEurekaResults"; + Param["HideEurekaSuggestions"] = "hideEurekaSuggestions"; + Param["HideAutocompleteSuggestions"] = "hideAutocompleteSuggestions"; + Param["HideLiveboardHeader"] = "hideLiveboardHeader"; + Param["ShowLiveboardDescription"] = "showLiveboardDescription"; + Param["ShowLiveboardTitle"] = "showLiveboardTitle"; + Param["ShowMaskedFilterChip"] = "showMaskedFilterChip"; + Param["IsLiveboardMasterpiecesEnabled"] = "isLiveboardMasterpiecesEnabled"; + Param["EnableNewChartLibrary"] = "muzeChartPhase1EnabledGA"; + Param["HiddenTabs"] = "hideTabs"; + Param["VisibleTabs"] = "visibleTabs"; + Param["HideTabPanel"] = "hideTabPanel"; + Param["HideSampleQuestions"] = "hideSampleQuestions"; + Param["WorksheetId"] = "worksheet"; + Param["Query"] = "query"; + Param["HideHomepageLeftNav"] = "hideHomepageLeftNav"; + Param["ModularHomeExperienceEnabled"] = "modularHomeExperience"; + Param["HomepageVersion"] = "homepageVersion"; + Param["ListPageVersion"] = "listpageVersion"; + Param["PendoTrackingKey"] = "additionalPendoKey"; + Param["LiveboardHeaderSticky"] = "isLiveboardHeaderSticky"; + Param["IsProductTour"] = "isProductTour"; + Param["HideSearchBarTitle"] = "hideSearchBarTitle"; + Param["HideSageAnswerHeader"] = "hideSageAnswerHeader"; + Param["HideSearchBar"] = "hideSearchBar"; + Param["ClientLogLevel"] = "clientLogLevel"; + Param["ExposeTranslationIDs"] = "exposeTranslationIDs"; + Param["OverrideNativeConsole"] = "overrideConsoleLogs"; + Param["enableAskSage"] = "enableAskSage"; + Param["CollapseSearchBarInitially"] = "collapseSearchBarInitially"; + Param["DataPanelCustomGroupsAccordionInitialState"] = "dataPanelCustomGroupsAccordionInitialState"; + Param["EnableCustomColumnGroups"] = "enableCustomColumnGroups"; + Param["DateFormatLocale"] = "dateFormatLocale"; + Param["NumberFormatLocale"] = "numberFormatLocale"; + Param["CurrencyFormat"] = "currencyFormat"; + Param["Enable2ColumnLayout"] = "enable2ColumnLayout"; + Param["IsFullAppEmbed"] = "isFullAppEmbed"; + Param["IsOnBeforeGetVizDataInterceptEnabled"] = "isOnBeforeGetVizDataInterceptEnabled"; + Param["FocusSearchBarOnRender"] = "focusSearchBarOnRender"; + Param["DisableRedirectionLinksInNewTab"] = "disableRedirectionLinksInNewTab"; + Param["HomePageSearchBarMode"] = "homePageSearchBarMode"; + Param["ShowLiveboardVerifiedBadge"] = "showLiveboardVerifiedBadge"; + Param["ShowLiveboardReverifyBanner"] = "showLiveboardReverifyBanner"; + Param["LiveboardHeaderV2"] = "isLiveboardHeaderV2Enabled"; + Param["HideIrrelevantFiltersInTab"] = "hideIrrelevantFiltersAtTabLevel"; + Param["IsEnhancedFilterInteractivityEnabled"] = "isLiveboardPermissionV2Enabled"; + Param["SpotterEnabled"] = "isSpotterExperienceEnabled"; + Param["IsUnifiedSearchExperienceEnabled"] = "isUnifiedSearchExperienceEnabled"; + Param["OverrideOrgId"] = "orgId"; + Param["OauthPollingInterval"] = "oAuthPollingInterval"; + Param["IsForceRedirect"] = "isForceRedirect"; + Param["DataSourceId"] = "dataSourceId"; + Param["preAuthCache"] = "preAuthCache"; + Param["ShowSpotterLimitations"] = "showSpotterLimitations"; + Param["CoverAndFilterOptionInPDF"] = "arePdfCoverFilterPageCheckboxesEnabled"; + Param["PrimaryAction"] = "primaryAction"; + Param["isSpotterAgentEmbed"] = "isSpotterAgentEmbed"; + Param["IsLiveboardStylingAndGroupingEnabled"] = "isLiveboardStylingAndGroupingEnabled"; + Param["IsLazyLoadingForEmbedEnabled"] = "isLazyLoadingForEmbedEnabled"; + Param["RootMarginForLazyLoad"] = "rootMarginForLazyLoad"; + Param["isPNGInScheduledEmailsEnabled"] = "isPNGInScheduledEmailsEnabled"; + Param["IsWYSIWYGLiveboardPDFEnabled"] = "isWYSIWYGLiveboardPDFEnabled"; + Param["isLiveboardXLSXCSVDownloadEnabled"] = "isLiveboardXLSXCSVDownloadEnabled"; + Param["isGranularXLSXCSVSchedulesEnabled"] = "isGranularXLSXCSVSchedulesEnabled"; + Param["isSendNowLiveboardSchedulingEnabled"] = "isSendNowLiveboardSchedulingEnabled"; + Param["isCentralizedLiveboardFilterUXEnabled"] = "isCentralizedLiveboardFilterUXEnabled"; + Param["isLinkParametersEnabled"] = "isLinkParametersEnabled"; + Param["EnablePastConversationsSidebar"] = "enablePastConversationsSidebar"; + Param["UpdatedSpotterChatPrompt"] = "updatedSpotterChatPrompt"; + Param["EnableStopAnswerGenerationEmbed"] = "enableStopAnswerGenerationEmbed"; + Param["SpotterSidebarTitle"] = "spotterSidebarTitle"; + Param["SpotterSidebarDefaultExpanded"] = "spotterSidebarDefaultExpanded"; + Param["SpotterChatRenameLabel"] = "spotterChatRenameLabel"; + Param["SpotterChatDeleteLabel"] = "spotterChatDeleteLabel"; + Param["SpotterDeleteConversationModalTitle"] = "spotterDeleteConversationModalTitle"; + Param["SpotterPastConversationAlertMessage"] = "spotterPastConversationAlertMessage"; + Param["SpotterDocumentationUrl"] = "spotterDocumentationUrl"; + Param["SpotterBestPracticesLabel"] = "spotterBestPracticesLabel"; + Param["SpotterConversationsBatchSize"] = "spotterConversationsBatchSize"; + Param["SpotterNewChatButtonTitle"] = "spotterNewChatButtonTitle"; + Param["IsThisPeriodInDateFiltersEnabled"] = "isThisPeriodInDateFiltersEnabled"; + Param["HideToolResponseCardBranding"] = "hideToolResponseCardBranding"; + Param["ToolResponseCardBrandingLabel"] = "toolResponseCardBrandingLabel"; + Param["EnableHomepageAnnouncement"] = "enableHomepageAnnouncement"; + Param["EnableLiveboardDataCache"] = "enableLiveboardDataCache"; + Param["SpotterFileUploadEnabled"] = "spotterFileUploadEnabled"; + Param["SpotterFileUploadFileTypes"] = "spotterFileUploadFileTypes"; +})(Param || (Param = {})); +/** + * ThoughtSpot application pages include actions and menu commands + * for various user-initiated operations. These actions are represented + * as enumeration members in the SDK. To control actions in the embedded view: + * - disabledActions — the action is grayed out and still visible, but non-interactive (user can see but not click). + * - hiddenActions — the action is completely removed from the UI (user cannot see it at all). + * - visibleActions — allowlist, only these actions are shown; all others are hidden. + * + * Use disabledActions to disable (gray out) an action. + * Use hiddenActions to hide (fully remove) an action. + * Use visibleActions to show only specific actions. + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... //other embed view config + * visibleActions: [Action.Save, Action.Edit, Action.Present, Action.Explore], + * disabledActions: [Action.Download], + * //hiddenActions: [], // Set either this or visibleActions + * }) + * ``` + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... //other embed view config + * //visibleActions: [], + * disabledActions: [Action.Download], + * hiddenActions: [Action.Edit, Action.Explore], + * }) + * ``` + * See also link:https://developers.thoughtspot.com/docs/actions[Developer Documentation]. + */ +var Action; +(function (Action) { + /** + * The **Save** action on an Answer or Liveboard. + * Allows users to save the changes. + * @example + * ```js + * disabledActions: [Action.Save] + * ``` + */ + Action["Save"] = "save"; + /** + * @hidden + */ + Action["Update"] = "update"; + /** + * @hidden + */ + Action["SaveUntitled"] = "saveUntitled"; + /** + * The **Save as View** action on the Answer + * page. Saves an Answer as a View object in the full + * application embedding mode. + * @example + * ```js + * disabledActions: [Action.SaveAsView] + * ``` + */ + Action["SaveAsView"] = "saveAsView"; + /** + * The **Make a copy** action on a Liveboard or Answer + * page. Creates a copy of the Liveboard. + * In LiveboardEmbed, the **Make a copy** action is not available for + * visualizations in the embedded Liveboard view. + * In AppEmbed, the **Make a copy** action is available on both + * Liveboards and visualizations. + * @example + * ```js + * disabledActions: [Action.MakeACopy] + * ``` + */ + Action["MakeACopy"] = "makeACopy"; + /** + * The **Copy and Edit** action on a Liveboard. + * This action is now replaced with `Action.MakeACopy`. + * @example + * ```js + * disabledActions: [Action.EditACopy] + * ``` + */ + Action["EditACopy"] = "editACopy"; + /** + * The **Copy link** menu action on a Liveboard visualization. + * Copies the visualization URL + * @example + * ```js + * disabledActions: [Action.CopyLink] + * ``` + */ + Action["CopyLink"] = "embedDocument"; + /** + * @hidden + */ + Action["ResetLayout"] = "resetLayout"; + /** + * The **Schedule** menu action on a Liveboard. + * Allows scheduling a Liveboard job, for example, + * sending periodic notifications. + * @example + * ```js + * disabledActions: [Action.Schedule] + * ``` + */ + Action["Schedule"] = "subscription"; + /** + * The **Manage schedules** menu action on a Liveboard. + * Allows users to manage scheduled Liveboard jobs. + * @example + * ```js + * disabledActions: [Action.SchedulesList] + * ``` + */ + Action["SchedulesList"] = "schedule-list"; + /** + * The **Share** action on a Liveboard, Answer, or Model. + * Allows users to share an object with other users and groups. + * @example + * ```js + * disabledActions: [Action.Share] + * ``` + */ + Action["Share"] = "share"; + /** + * The **Add filter** action on a Liveboard page. + * Allows adding filters to visualizations on a Liveboard. + * @example + * ```js + * disabledActions: [Action.AddFilter] + * ``` + */ + Action["AddFilter"] = "addFilter"; + /** + * The **Add Data Panel Objects** action on the data panel v2. + * Allows to show action menu to add different objects (such as + * formulas, Parameters) in data panel new experience. + * @example + * ```js + * disabledActions: [Action.AddDataPanelObjects] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl, 10.1.0.sw + */ + Action["AddDataPanelObjects"] = "addDataPanelObjects"; + /** + * The filter configuration options for a Liveboard. + * The configuration options are available when adding + * filters on a Liveboard. + * + * @example + * ```js + * disabledActions: [Action.ConfigureFilter] + * ``` + */ + Action["ConfigureFilter"] = "configureFilter"; + /** + * The **Collapse data sources** icon on the Search page. + * Collapses the panel showing data sources. + * + * @example + * ```js + * disabledActions: [Action.CollapseDataPanel] + * ``` + * @version SDK: 1.1.0 | ThoughtSpot Cloud: ts7.may.cl, 8.4.1.sw + */ + Action["CollapseDataSources"] = "collapseDataSources"; + /** + * The **Collapse data panel** icon on the Search page. + * Collapses the data panel view. + * + * @version SDK: 1.34.0 | ThoughtSpot Cloud: 10.3.0.cl + * + * @example + * ```js + * disabledActions: [Action.CollapseDataPanel] + * ``` + */ + Action["CollapseDataPanel"] = "collapseDataPanel"; + /** + * The **Choose sources** button on Search page. + * Allows selecting data sources for search queries. + * @example + * ```js + * disabledActions: [Action.ChooseDataSources] + * ``` + */ + Action["ChooseDataSources"] = "chooseDataSources"; + /** + * The **Create formula** action on a Search or Answer page. + * Allows adding formulas to an Answer. + * @example + * ```js + * disabledActions: [Action.AddFormula] + * ``` + */ + Action["AddFormula"] = "addFormula"; + /** + * The **Add parameter** action on a Liveboard or Answer. + * Allows adding Parameters to a Liveboard or Answer. + * @example + * ```js + * disabledActions: [Action.AddParameter] + * ``` + */ + Action["AddParameter"] = "addParameter"; + /** + * The **Add Column Set** action on a Answer. + * Allows adding column sets to a Answer. + * @example + * ```js + * disabledActions: [Action.AddColumnSet] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl, 10.1.0.sw + */ + Action["AddColumnSet"] = "addSimpleCohort"; + /** + * The **Add Query Set** action on a Answer. + * Allows adding query sets to a Answer. + * @example + * ```js + * disabledActions: [Action.AddQuerySet] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl, 10.1.0.sw + */ + Action["AddQuerySet"] = "addAdvancedCohort"; + /** + * @hidden + */ + Action["SearchOnTop"] = "searchOnTop"; + /** + * The **SpotIQ analyze** menu action on a visualization or + * Answer page. + * @example + * ```js + * disabledActions: [Action.SpotIQAnalyze] + * ``` + */ + Action["SpotIQAnalyze"] = "spotIQAnalyze"; + /** + * @hidden + */ + Action["ExplainInsight"] = "explainInsight"; + /** + * @hidden + */ + Action["SpotIQFollow"] = "spotIQFollow"; + /** + * The Share action for a Liveboard visualization. + */ + Action["ShareViz"] = "shareViz"; + /** + * @hidden + */ + Action["ReplaySearch"] = "replaySearch"; + /** + * The **Show underlying data** menu action on a + * visualization or Answer page. + * Displays detailed information and raw data + * for a given visualization. + * @example + * ```js + * disabledActions: [Action.ShowUnderlyingData] + * ``` + */ + Action["ShowUnderlyingData"] = "showUnderlyingData"; + /** + * The **Download** menu action on Liveboard + * visualizations and Answers. + * Allows downloading a visualization or Answer. + * @example + * ```js + * disabledActions: [Action.DownloadAsPng] + * ``` + */ + Action["Download"] = "download"; + /** + * The **Download** > **PNG** menu action for charts on a Liveboard + * or Answer page. + * Downloads a visualization or Answer as a PNG file. + * @example + * ```js + * disabledActions: [Action.DownloadAsPng] + * ``` + */ + Action["DownloadAsPng"] = "downloadAsPng"; + /** + * + *The **Download PDF** action that downloads a Liveboard, + * visualization, or Answer as a PDF file. + * + * **NOTE**: The **Download** > **PDF** option is available for + * tables in visualizations and Answers. + * @example + * ```js + * disabledActions: [Action.DownloadAsPdf] + * ``` + */ + Action["DownloadAsPdf"] = "downloadAsPdf"; + /** + * The **Download** > **CSV** menu action for tables on a Liveboard + * or Answer page. + * Downloads a visualization or Answer in the XLSX format. + * @example + * ```js + * disabledActions: [Action.DownloadAsCsv] + * ``` + */ + Action["DownloadAsCsv"] = "downloadAsCSV"; + /** + * The **Download** > **XLSX** menu action for tables on a Liveboard + * or Answer page. + * Downloads a visualization or Answer in the XLSX format. + * @example + * ```js + * disabledActions: [Action.DownloadAsXlsx] + * ``` + */ + Action["DownloadAsXlsx"] = "downloadAsXLSX"; + /** + * The **Download Liveboard** menu action on a Liveboard. + * Allows downloading the entire Liveboard. + * @example + * ```js + * disabledActions: [Action.DownloadLiveboard] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + Action["DownloadLiveboard"] = "downloadLiveboard"; + /** + * The **Download Liveboard as Continuous PDF** menu action on a Liveboard. + * Allows downloading the entire Liveboard as a continuous PDF. + * @example + * ```js + * disabledActions: [Action.DownloadLiveboardAsContinuousPDF] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + Action["DownloadLiveboardAsContinuousPDF"] = "downloadLiveboardAsContinuousPDF"; + /** + * The Download Liveboard as A4 PDF menu action on a Liveboard. + * Allows downloading the entire Liveboard as an A4 PDF. + * Requires {@link Action.DownloadLiveboard} as a parent action when + * {@link LiveboardViewConfig.isLiveboardXLSXCSVDownloadEnabled} or + * {@link LiveboardViewConfig.isContinuousLiveboardPDFEnabled} flags are enabled. + * Use this instead of {@link Action.DownloadAsPdf} when either flag is on. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * disabledActions: [Action.DownloadLiveboardAsA4Pdf] + * ``` + */ + Action["DownloadLiveboardAsA4Pdf"] = "downloadLiveboardAsA4Pdf"; + /** + * The **Download Liveboard as XLSX** menu action on a Liveboard. + * Allows downloading the entire Liveboard as an XLSX file. + * @example + * ```js + * disabledActions: [Action.DownloadLiveboardAsXlsx] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + Action["DownloadLiveboardAsXlsx"] = "downloadLiveboardAsXlsx"; + /** + * The **Download Liveboard as CSV** menu action on a Liveboard. + * Allows downloading the entire Liveboard as a CSV file. + * @example + * ```js + * disabledActions: [Action.DownloadLiveboardAsCsv] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + Action["DownloadLiveboardAsCsv"] = "downloadLiveboardAsCsv"; + /** + * @hidden + */ + Action["DownloadTrace"] = "downloadTrace"; + /** + * The **Export TML** menu action on a Liveboard, Answer, and + * the Data Workspace pages for data objects and connections. + * + * Allows exporting an object as a TML file. + * + * @example + * ```js + * disabledActions: [Action.ExportTML] + * ``` + */ + Action["ExportTML"] = "exportTSL"; + /** + * The **Import TML** menu action on the + * *Data Workspace* > *Utilities* page. + * Imports TML representation of ThoughtSpot objects. + * @example + * ```js + * disabledActions: [Action.ImportTML] + * ``` + */ + Action["ImportTML"] = "importTSL"; + /** + * The **Update TML** menu action for Liveboards and Answers. + * Updates TML representation of ThoughtSpot objects. + * @example + * ```js + * disabledActions: [Action.UpdateTML] + * ``` + */ + Action["UpdateTML"] = "updateTSL"; + /** + * The **Edit TML** menu action for Liveboards and Answers. + * Opens the TML editor. + * @example + * ```js + * disabledActions: [Action.EditTML] + * ``` + */ + Action["EditTML"] = "editTSL"; + /** + * The **Present** menu action for Liveboards and Answers. + * Allows presenting a Liveboard or visualization in + * slideshow mode. + * @example + * ```js + * disabledActions: [Action.Present] + * ``` + */ + Action["Present"] = "present"; + /** + * The visualization tile resize option. + * Also available via More `...` options menu on a visualization. + * Allows resizing visualization tiles and switching + * between different preset layout option. + * + * @example + * ```js + * disabledActions: [Action.ToggleSize] + * ``` + */ + Action["ToggleSize"] = "toggleSize"; + /** + * The *Edit* action on the Liveboard page and in the + * visualization menu. + * Opens a Liveboard or visualization in edit mode. + * @example + * ```js + * disabledActions: [Action.Edit] + * ``` + */ + Action["Edit"] = "edit"; + /** + * The text edit option for Liveboard and visualization titles. + * @example + * ```js + * disabledActions: [Action.EditTitle] + * ``` + */ + Action["EditTitle"] = "editTitle"; + /** + * The **Delete** action on a Liveboard, *Liveboards* and + * *Answers* list pages in full application embedding. + * + * @example + * ```js + * disabledActions: [Action.Remove] + * ``` + */ + Action["Remove"] = "delete"; + /** + * @hidden + */ + Action["Ungroup"] = "ungroup"; + /** + * @hidden + */ + Action["Describe"] = "describe"; + /** + * @hidden + */ + Action["Relate"] = "relate"; + /** + * @hidden + */ + Action["CustomizeHeadlines"] = "customizeHeadlines"; + /** + * @hidden + */ + Action["PinboardInfo"] = "pinboardInfo"; + /** + * The **Show Liveboard details** menu action on a Liveboard. + * Displays details such as the name, description, and + * author of the Liveboard, and timestamp of Liveboard creation + * and update. + * @example + * ```js + * disabledActions: [Action.LiveboardInfo] + * ``` + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + Action["LiveboardInfo"] = "pinboardInfo"; + /** + * @hidden + */ + Action["SendAnswerFeedback"] = "sendFeedback"; + /** + * @hidden + */ + Action["DownloadEmbraceQueries"] = "downloadEmbraceQueries"; + /** + * The **Pin** menu action on an Answer or + * Search results page. + * @example + * ```js + * disabledActions: [Action.Pin] + * ``` + */ + Action["Pin"] = "pin"; + /** + * @hidden + */ + Action["AnalysisInfo"] = "analysisInfo"; + /** + * The **Schedule** menu action on a Liveboard. + * Allows scheduling a Liveboard job. + * @example + * ```js + * disabledActions: [Action.Subscription] + * ``` + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + Action["Subscription"] = "subscription"; + /** + * The **Explore** action on Liveboard visualizations + * @example + * ```js + * disabledActions: [Action.Explore] + * ``` + */ + Action["Explore"] = "explore"; + /** + * The contextual menu action to include a specific data point + * when drilling down a table or chart on an Answer. + * + * @example + * ```js + * disabledActions: [Action.DrillInclude] + * ``` + */ + Action["DrillInclude"] = "context-menu-item-include"; + /** + * The contextual menu action to exclude a specific data point + * when drilling down a table or chart on an Answer. + * @example + * ```js + * disabledActions: [Action.DrillInclude] + * ``` + */ + Action["DrillExclude"] = "context-menu-item-exclude"; + /** + * The **Copy to clipboard** menu action on tables in an Answer + * or Liveboard. + * Copies the selected data point. + * @example + * ```js + * disabledActions: [Action.CopyToClipboard] + * ``` + */ + Action["CopyToClipboard"] = "context-menu-item-copy-to-clipboard"; + Action["CopyAndEdit"] = "context-menu-item-copy-and-edit"; + /** + * @hidden + */ + Action["DrillEdit"] = "context-menu-item-edit"; + Action["EditMeasure"] = "context-menu-item-edit-measure"; + Action["Separator"] = "context-menu-item-separator"; + /** + * The **Drill down** menu action on Answers and Liveboard + * visualizations. + * Allows drilling down to a specific data point on a chart or table. + * @example + * ```js + * disabledActions: [Action.DrillDown] + * ``` + */ + Action["DrillDown"] = "DRILL"; + /** + * The request access action on Liveboards. + * Allows users with view permissions to request edit access to a Liveboard. + * @example + * ```js + * disabledActions: [Action.RequestAccess] + * ``` + */ + Action["RequestAccess"] = "requestAccess"; + /** + * Controls the display and availability of the **Query visualizer** and + * **Query SQL** buttons in the Query details panel on the Answer page. + * + * **Query visualizer** - Displays the tables and filters used in the search query. + * **Query SQL** - Displays the SQL statements used to retrieve data for the query. + * + * Note: This action ID only affects the visibility of the buttons within the + * Query details panel. It does not control the visibility + * of the query details icon on the Answer page. + * @example + * ```js + * disabledActions: [Action.QueryDetailsButtons] + * ``` + */ + Action["QueryDetailsButtons"] = "queryDetailsButtons"; + /** + * The **Delete** action for Answers in the full application + * embedding mode. + * @example + * ```js + * disabledActions: [Action.AnswerDelete] + * ``` + * @version SDK: 1.9.0 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + */ + Action["AnswerDelete"] = "onDeleteAnswer"; + /** + * The chart switcher icon on Answer page and + * visualizations in edit mode. + * Allows switching to the table or chart mode + * when editing a visualization. + * @example + * ```js + * disabledActions: [Action.AnswerChartSwitcher] + * ``` + * @version SDK: 1.9.0 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + */ + Action["AnswerChartSwitcher"] = "answerChartSwitcher"; + /** + * The Favorites icon (*) for Answers, + * Liveboard, and data objects like Model, + * Tables and Views. + * Allows adding an object to the user's favorites list. + * @example + * ```js + * disabledActions: [Action.AddToFavorites] + * ``` + * @version SDK: 1.9.0 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + */ + Action["AddToFavorites"] = "addToFavorites"; + /** + * The edit icon on Liveboards (Classic experience). + * @example + * ```js + * disabledActions: [Action.EditDetails] + * ``` + * @version SDK: 1.9.0 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + */ + Action["EditDetails"] = "editDetails"; + /** + * The *Create alert* action for KPI charts. + * Allows users to schedule threshold-based alerts + * for KPI charts. + * @example + * ```js + * disabledActions: [Action.CreateMonitor] + * ``` + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + */ + Action["CreateMonitor"] = "createMonitor"; + /** + * @version SDK: 1.11.1 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @deprecated This action is deprecated. It was used for reporting errors. + * @example + * ```js + * disabledActions: [Action.ReportError] + * ``` + */ + Action["ReportError"] = "reportError"; + /** + * The **Sync to sheets** action on Answers and Liveboard visualizations. + * Allows sending data to a Google Sheet. + * @example + * ```js + * disabledActions: [Action.SyncToSheets] + * ``` + * @version SDK: 1.18.0 | ThoughtSpot: 8.10.0.cl, 9.0.1.sw + */ + Action["SyncToSheets"] = "sync-to-sheets"; + /** + * The **Sync to other apps** action on Answers and Liveboard visualizations. + * Allows sending data to third-party apps like Slack, Salesforce, + * Microsoft Teams, and so on. + * @example + * ```js + * disabledActions: [Action.SyncToOtherApps] + * ``` + * @version SDK: 1.18.0 | ThoughtSpot: 8.10.0.cl, 9.0.1.sw + */ + Action["SyncToOtherApps"] = "sync-to-other-apps"; + /** + * The **Manage pipelines** action on Answers and Liveboard visualizations. + * Allows users to manage data sync pipelines to third-party apps. + * @example + * ```js + * disabledActions: [Action.ManagePipelines] + * ``` + * @version SDK: 1.18.0 | ThoughtSpot: 8.10.0.cl, 9.0.1.sw + */ + Action["ManagePipelines"] = "manage-pipeline"; + /** + * The **Filter** action on Liveboard visualizations. + * Allows users to apply cross-filters on a Liveboard. + * @example + * ```js + * disabledActions: [Action.CrossFilter] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.8.0.sw + */ + Action["CrossFilter"] = "context-menu-item-cross-filter"; + /** + * The **Sync to Slack** action on Liveboard visualizations. + * Allows sending data to third-party apps like Slack. + * @example + * ```js + * disabledActions: [Action.SyncToSlack] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot Cloud: 10.1.0.cl + */ + Action["SyncToSlack"] = "syncToSlack"; + /** + * The **Sync to Teams** action on Liveboard visualizations. + * Allows sending data to third-party apps like Microsoft Teams. + * @example + * ```js + * disabledActions: [Action.SyncToTeams] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot Cloud: 10.1.0.cl + */ + Action["SyncToTeams"] = "syncToTeams"; + /** + * The **Remove** action that appears when cross filters are applied + * on a Liveboard. + * Removes filters applied to a visualization. + * @example + * ```js + * disabledActions: [Action.RemoveCrossFilter] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["RemoveCrossFilter"] = "context-menu-item-remove-cross-filter"; + /** + * The **Aggregate** option in the chart axis or the + * table column customization menu. + * Provides aggregation options to analyze the data on a chart or table. + * @example + * ```js + * disabledActions: [Action.AxisMenuAggregate] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuAggregate"] = "axisMenuAggregate"; + /** + * The **Time bucket** option in the chart axis or table column + * customization menu. + * Allows defining time metric for date comparison. + * @example + * ```js + * disabledActions: [Action.AxisMenuTimeBucket] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuTimeBucket"] = "axisMenuTimeBucket"; + /** + * The **Filter** action in the chart axis or table column + * customization menu. + * Allows adding, editing, or removing filters. + * + * @example + * ```js + * disabledActions: [Action.AxisMenuFilter] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuFilter"] = "axisMenuFilter"; + /** + * The **Conditional formatting** action on chart or table. + * Allows adding rules for conditional formatting of data + * points on a chart or table. + * @example + * ```js + * disabledActions: [Action.AxisMenuConditionalFormat] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuConditionalFormat"] = "axisMenuConditionalFormat"; + /** + * The **Sort** menu action on a table or chart axis + * Sorts data in ascending or descending order. + * Allows adding, editing, or removing filters. + * @example + * ```js + * disabledActions: [Action.AxisMenuConditionalFormat] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuSort"] = "axisMenuSort"; + /** + * The **Group** option in the chart axis or table column + * customization menu. + * Allows grouping data points if the axes use the same + * unit of measurement and a similar scale. + * @example + * ```js + * disabledActions: [Action.AxisMenuGroup] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuGroup"] = "axisMenuGroup"; + /** + * The **Position** option in the axis customization menu. + * Allows changing the position of the axis to the + * left or right side of the chart. + * @example + * ```js + * disabledActions: [Action.AxisMenuPosition] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuPosition"] = "axisMenuPosition"; + /** + * The **Rename** option in the chart axis or table column customization menu. + * Renames the axis label on a chart or the column header on a table. + * @example + * ```js + * disabledActions: [Action.AxisMenuRename] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuRename"] = "axisMenuRename"; + /** + * The **Edit** action in the axis customization menu. + * Allows editing the axis name, position, minimum and maximum values, + * and format of a column. + * @example + * ```js + * disabledActions: [Action.AxisMenuEdit] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuEdit"] = "axisMenuEdit"; + /** + * The **Number format** action to customize the format of + * the data labels on a chart or table. + * @example + * ```js + * disabledActions: [Action.AxisMenuNumberFormat] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuNumberFormat"] = "axisMenuNumberFormat"; + /** + * The **Text wrapping** action on a table. + * Wraps or clips column text on a table. + * @example + * ```js + * disabledActions: [Action.AxisMenuTextWrapping] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuTextWrapping"] = "axisMenuTextWrapping"; + /** + * The **Remove** action in the chart axis or table column + * customization menu. + * Removes the data labels from a chart or the column of a + * table visualization. + * @example + * ```js + * disabledActions: [Action.AxisMenuRemove] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuRemove"] = "axisMenuRemove"; + /** + * The **Compare with** action in the chart axis customization menu. + * Allows comparing data across dimensions or measures. + * @example + * ```js + * disabledActions: [Action.AxisMenuCompare] + * ``` + * @version SDK: 1.50.0 | ThoughtSpot: 26.7.0.cl + */ + Action["AxisMenuCompare"] = "axisMenuCompare"; + /** + * The **Merge with** action in the chart axis customization menu. + * Allows merging data across dimensions or measures. + * @example + * ```js + * disabledActions: [Action.AxisMenuMerge] + * ``` + * @version SDK: 1.50.0 | ThoughtSpot: 26.7.0.cl + */ + Action["AxisMenuMerge"] = "axisMenuMerge"; + /** + * @hidden + */ + Action["InsertInToSlide"] = "insertInToSlide"; + /** + * The **Rename** menu action on Liveboards and visualizations. + * Allows renaming a Liveboard or visualization. + * @example + * ```js + * disabledActions: [Action.RenameModalTitleDescription] + * ``` + * @version SDK: 1.23.0 | ThoughtSpot: 9.4.0.cl, 9.8.0.sw + */ + Action["RenameModalTitleDescription"] = "renameModalTitleDescription"; + /** + * The *Request verification* action on a Liveboard. + * Initiates a request for Liveboard verification. + * @example + * ```js + * disabledActions: [Action.RequestVerification] + * ``` + * @version SDK: 1.25.0 | ThoughtSpot: 9.6.0.cl, 10.1.0.sw + */ + Action["RequestVerification"] = "requestVerification"; + /** + * + * Allows users to mark a Liveboard as verified. + * @example + * ```js + * disabledActions: [Action.MarkAsVerified] + * ``` + * @version SDK: 1.25.0 | ThoughtSpot: 9.6.0.cl, 10.1.0.sw + */ + Action["MarkAsVerified"] = "markAsVerified"; + /** + * The **Add Tab** action on a Liveboard. + * Allows adding a new tab to a Liveboard view. + * @example + * ```js + * disabledActions: [Action.AddTab] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + */ + Action["AddTab"] = "addTab"; + /** + * + * Initiates contextual change analysis on KPI charts. + * @example + * ```js + * disabledActions: [Action.EnableContextualChangeAnalysis] + * ``` + * @version SDK: 1.25.0 | ThoughtSpot Cloud: 9.6.0.cl + */ + Action["EnableContextualChangeAnalysis"] = "enableContextualChangeAnalysis"; + /** + * Action ID to hide or disable Iterative Change Analysis option + * in the contextual change analysis Insight charts context menu. + * + * @example + * ```js + * disabledActions: [Action.EnableIterativeChangeAnalysis] + * ``` + * @version SDK: 1.41.0 | ThoughtSpot Cloud: 9.12.0.cl + */ + Action["EnableIterativeChangeAnalysis"] = "enableIterativeChangeAnalysis"; + /** + * Action ID to hide or disable Natural Language Search query. + * + * @example + * ```js + * disabledActions: [Action.ShowSageQuery] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot Cloud: 9.7.0.cl + */ + Action["ShowSageQuery"] = "showSageQuery"; + /** + * + * Action ID to hide or disable the edit option for the + * results generated from the + * Natural Language Search query. + * + * @example + * ```js + * disabledActions: [Action.EditSageAnswer] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot Cloud: 9.7.0.cl + */ + Action["EditSageAnswer"] = "editSageAnswer"; + /** + * The feedback widget for AI-generated Answers. + * Allows users to send feedback on the Answers generated + * from a Natural Language Search query. + * + * @example + * ```js + * disabledActions: [Action.SageAnswerFeedback] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl + */ + Action["SageAnswerFeedback"] = "sageAnswerFeedback"; + /** + * + * @example + * ```js + * disabledActions: [Action.ModifySageAnswer] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl + */ + Action["ModifySageAnswer"] = "modifySageAnswer"; + /** + * The **Move to Tab** menu action on visualizations in Liveboard edit mode. + * Allows moving a visualization to a different tab. + * @example + * ```js + * disabledActions: [Action.MoveToTab] + * ``` + */ + Action["MoveToTab"] = "onContainerMove"; + /** + * The **Manage Alerts** menu action on KPI visualizations. + * Allows creating, viewing, and editing monitor + * alerts for a KPI chart. + * + * @example + * ```js + * disabledActions: [Action.ManageMonitor] + * ``` + */ + Action["ManageMonitor"] = "manageMonitor"; + /** + * The Liveboard Personalised Views dropdown. + * Allows navigating to a personalized Liveboard View. + * This action is deprecated. Use {@link Action.PersonalizedViewsDropdown} instead. + * @example + * ```js + * disabledActions: [Action.PersonalisedViewsDropdown] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 10.1.0.sw + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + Action["PersonalisedViewsDropdown"] = "personalisedViewsDropdown"; + /** + * The Liveboard Personalized Views dropdown. + * Allows navigating to a personalized Liveboard View. + * @example + * ```js + * disabledActions: [Action.PersonalizedViewsDropdown] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + Action["PersonalizedViewsDropdown"] = "personalisedViewsDropdown"; + /** + * Action ID for show or hide the user details on a + * Liveboard (Recently visited / social proof) + * @example + * ```js + * disabledActions: [Action.LiveboardUsers] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 10.1.0.sw + */ + Action["LiveboardUsers"] = "liveboardUsers"; + /** + * Action ID for the Parent TML action + * The parent action **TML** must be included to access TML-related options + * within the cascading menu (specific to the Answer page) + * @example + * ```js + * // to include specific TML actions + * visibleActions: [Action.TML, Action.ExportTML, Action.EditTML] + * + * ``` + * @example + * ```js + * hiddenAction: [Action.TML] // hide all TML actions + * disabledActions: [Action.TML] // to disable all TML actions + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: 9.12.0.cl, 10.1.0.sw + */ + Action["TML"] = "tml"; + /** + * The **Create Liveboard* action on + * the Liveboards page and the Pin modal. + * Allows users to create a Liveboard. + * + * @example + * ```js + * hiddenAction: [Action.CreateLiveboard] + * disabledActions: [Action.CreateLiveboard] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot: 10.1.0.cl, 10.1.0.sw + */ + Action["CreateLiveboard"] = "createLiveboard"; + /** + * Action ID for to hide or disable the + * Verified Liveboard banner. + * @example + * ```js + * hiddenAction: [Action.VerifiedLiveboard] + * ``` + * @version SDK: 1.29.0 | ThoughtSpot: 9.10.0.cl, 10.1.0.sw + */ + Action["VerifiedLiveboard"] = "verifiedLiveboard"; + /** + * Action ID for the *Ask Sage* In Natural Language Search embed, + * *Spotter* in Liveboard, full app, and Spotter embed. + * + * Allows initiating a conversation with ThoughtSpot AI analyst. + * + * @example + * ```js + * hiddenAction: [Action.AskAi] + * ``` + * @version SDK: 1.29.0 | ThoughtSpot Cloud: 9.12.0.cl + */ + Action["AskAi"] = "AskAi"; + /** + * The **Add KPI to Watchlist** action on Home page watchlist. + * Adds a KPI chart to the watchlist on the Home page. + * @example + * ```js + * disabledActions: [Action.AddToWatchlist] + * ``` + * @version SDK: 1.27.9 | ThoughtSpot Cloud: 9.12.5.cl + */ + Action["AddToWatchlist"] = "addToWatchlist"; + /** + * The **Remove from watchlist** menu action on KPI watchlist. + * Removes a KPI chart from the watchlist on the Home page. + * @example + * ```js + * disabledActions: [Action.RemoveFromWatchlist] + * ``` + * @version SDK: 1.27.9 | ThoughtSpot: 9.12.5.cl + */ + Action["RemoveFromWatchlist"] = "removeFromWatchlist"; + /** + * The **Organize Favourites** action on Homepage + * *Favorites* module. + * This action is deprecated. Use {@link Action.OrganizeFavorites} instead. + * + * @example + * ```js + * disabledActions: [Action.OrganiseFavourites] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + Action["OrganiseFavourites"] = "organiseFavourites"; + /** + * The **Organize Favorites** action on Homepage + * *Favorites* module. + * + * @example + * ```js + * disabledActions: [Action.OrganizeFavorites] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + Action["OrganizeFavorites"] = "organiseFavourites"; + /** + * The **AI Highlights** action on a Liveboard. + * + * @example + * ```js + * hiddenAction: [Action.AIHighlights] + * ``` + * @version SDK: 1.27.10 | ThoughtSpot Cloud: 9.12.5.cl + */ + Action["AIHighlights"] = "AIHighlights"; + /** + * The *Edit* action on the *Liveboard Schedules* page + * (new Homepage experience). + * Allows editing Liveboard schedules. + * + * @example + * ```js + * disabledActions: [Action.EditScheduleHomepage] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot Cloud: 10.3.0.cl + */ + Action["EditScheduleHomepage"] = "editScheduleHomepage"; + /** + * The *Pause* action on the *Liveboard Schedules* page + * Pauses a scheduled Liveboard job. + * @example + * ```js + * disabledActions: [Action.PauseScheduleHomepage] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot Cloud: 10.3.0.cl + */ + Action["PauseScheduleHomepage"] = "pauseScheduleHomepage"; + /** + * The **View run history** action **Liveboard Schedules** page. + * Allows viewing schedule run history. + * @example + * ```js + * disabledActions: [Action.ViewScheduleRunHomepage] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot: 10.3.0.cl + */ + Action["ViewScheduleRunHomepage"] = "viewScheduleRunHomepage"; + /** + * Action ID to hide or disable the + * unsubscribe option for Liveboard schedules. + * @example + * ```js + * disabledActions: [Action.UnsubscribeScheduleHomepage] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot: 10.3.0.cl + */ + Action["UnsubscribeScheduleHomepage"] = "unsubscribeScheduleHomepage"; + /** + * The **Manage Tags** action on Homepage Favourite Module. + * @example + * ```js + * disabledActions: [Action.ManageTags] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot Cloud: 10.3.0.cl + */ + Action["ManageTags"] = "manageTags"; + /** + * The **Delete** action on the **Liveboard Schedules* page. + * Deletes a Liveboard schedule. + * @example + * ```js + * disabledActions: [Action.DeleteScheduleHomepage] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot: 10.3.0.cl + */ + Action["DeleteScheduleHomepage"] = "deleteScheduleHomepage"; + /** + * The **Analyze CTA** action on KPI chart. + * @example + * ```js + * disabledActions: [Action.KPIAnalysisCTA] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot Cloud: 10.3.0.cl + */ + Action["KPIAnalysisCTA"] = "kpiAnalysisCTA"; + /** + * Action ID for disabling chip reorder in Answer and Liveboard + * @example + * ```js + * const disabledActions = [Action.DisableChipReorder] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + Action["DisableChipReorder"] = "disableChipReorder"; + /** + * Action ID to show, hide, or disable filters + * in a Liveboard tab. + * + * @example + * ```js + * hiddenAction: [Action.ChangeFilterVisibilityInTab] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + Action["ChangeFilterVisibilityInTab"] = "changeFilterVisibilityInTab"; + /** + * The **Data model instructions** button on the Spotter interface. + * Allows opening the data model instructions modal. + * + * @example + * ```js + * hiddenAction: [Action.DataModelInstructions] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["DataModelInstructions"] = "DataModelInstructions"; + /** + * The **Preview data** button on the Spotter interface. + * Allows previewing the data used for Spotter queries. + * + * @example + * ```js + * hiddenAction: [Action.PreviewDataSpotter] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + Action["PreviewDataSpotter"] = "previewDataSpotter"; + /** + * The **Reset** link on the Spotter interface. + * Resets the conversation with Spotter. + * + * @example + * ```js + * hiddenAction: [Action.ResetSpotterChat] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + Action["ResetSpotterChat"] = "resetSpotterChat"; + /** + * Action ID for hide or disable the + * Spotter feedback widget. + * + * @example + * ```js + * hiddenAction: [Action.SpotterFeedback] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + Action["SpotterFeedback"] = "spotterFeedback"; + /** + * Action ID for hide or disable + * the previous prompt edit option in Spotter. + * + * @example + * ```js + * hiddenAction: [Action.EditPreviousPrompt] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + Action["EditPreviousPrompt"] = "editPreviousPrompt"; + /** + * Action ID for hide or disable + * the previous prompt deletion option in Spotter. + * + * @example + * ```js + * hiddenAction: [Action.DeletePreviousPrompt] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + Action["DeletePreviousPrompt"] = "deletePreviousPrompt"; + /** + * Action ID for hide or disable editing tokens generated from + * Spotter results. + * @example + * ```js + * hiddenAction: [Action.EditTokens] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + Action["EditTokens"] = "editTokens"; + /** + * Action ID for hiding rename option for Column rename + * @example + * ```js + * hiddenAction: [Action.ColumnRename] + * ``` + * @version SDK: 1.37.0 | ThoughtSpot Cloud: 10.8.0.cl + */ + Action["ColumnRename"] = "columnRename"; + /** + * Action ID for hide checkboxes for include or exclude + * cover and filter pages in the Liveboard PDF + * @example + * ```js + * hiddenAction: [Action.CoverAndFilterOptionInPDF] + * ``` + * @version SDK: 1.37.0 | ThoughtSpot Cloud: 10.8.0.cl + */ + Action["CoverAndFilterOptionInPDF"] = "coverAndFilterOptionInPDF"; + /** + * Action ID to hide or disable the Coaching workflow in Spotter conversations. + * When disabled, users cannot access **Add to Coaching** workflow in conversation. + * The **Add to Coaching** feature allows adding reference questions and + * business terms to improve Spotter’s responses. This feature is generally available + * (GA) from version 26.2.0.cl and enabled by default on embed deployments. + * @example + * ```js + * hiddenAction: [Action.InConversationTraining] + * disabledActions: [Action.InConversationTraining] + * + * ``` + * @version SDK: 1.39.0 | ThoughtSpot Cloud: 10.10.0.cl + */ + Action["InConversationTraining"] = "InConversationTraining"; + /** + * Action ID to hide the warnings banner in + * Spotter results. It's an EA feature and + * handled by LD. + * @example + * ```js + * hiddenAction: [Action.SpotterWarningsBanner] + * ``` + * @version SDK: 1.41.0 | ThoughtSpot Cloud: 10.13.0.cl + */ + Action["SpotterWarningsBanner"] = "SpotterWarningsBanner"; + /** + * Action ID to hide the warnings border on the knowledge + * card in Spotter results. It's an EA feature and + * handled by LD. + * @example + * ```js + * hiddenAction: [Action.SpotterWarningsOnTokens] + * ``` + * @version SDK: 1.41.0 | ThoughtSpot Cloud: 10.13.0.cl + */ + Action["SpotterWarningsOnTokens"] = "SpotterWarningsOnTokens"; + /** + * Action ID to disable the click event handler on knowledge + * card in Spotter results. It's an EA feature and + * handled by LD. + * @example + * ```js + * hiddenAction: [Action.SpotterTokenQuickEdit] + * ``` + * @version SDK: 1.41.0 | ThoughtSpot Cloud: 10.13.0.cl + */ + Action["SpotterTokenQuickEdit"] = "SpotterTokenQuickEdit"; + /** + * The **PNG screenshot in email** option in the schedule email dialog. + * Includes a PNG screenshot in the notification email body. + * @example + * ```js + * disabledActions: [Action.PngScreenshotInEmail] + * ``` + * ``` + * @version SDK: 1.42.0 | ThoughtSpot Cloud: 10.14.0.cl + */ + Action["PngScreenshotInEmail"] = "pngScreenshotInEmail"; + /** + * The **Remove attachment** action in the schedule email dialog. + * Removes an attachment from the email configuration. + * @example + * ```js + * disabledActions: [Action.RemoveAttachment] + * ``` + * ``` + * ``` + * @version SDK: 1.42.0 | ThoughtSpot Cloud: 10.14.0.cl + */ + Action["RemoveAttachment"] = "removeAttachment"; + /** + * The **Style panel** on a Liveboard. + * Controls the visibility of the Liveboard style panel. + * @example + * ```js + * hiddenActions: [Action.LiveboardStylePanel] + * ``` + * @version SDK: 1.43.0 | ThoughtSpot Cloud: 10.15.0.cl + */ + Action["LiveboardStylePanel"] = "liveboardStylePanel"; + /** + * The **Publish** action for Liveboards, Answers and Models. + * Opens the publishing modal. It's a parent action for the + * **Manage Publishing** and **Unpublish** actions if the object + * is already published, otherwise appears standalone. + * @example + * ```js + * hiddenActions: [Action.Publish] + * disabledActions: [Action.Publish] + * ``` + * @version SDK: 1.45.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Action["Publish"] = "publish"; + /** + * The **Manage Publishing** action for Liveboards, Answers and Models. + * Opens the same publishing modal as the **Publish** action. + * Appears as a child action to the **Publish** action if the + * object is already published. + * @example + * ```js + * hiddenActions: [Action.ManagePublishing] + * disabledActions: [Action.ManagePublishing] + * ``` + * @version SDK: 1.45.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Action["ManagePublishing"] = "managePublishing"; + /** + * The **Unpublish** action for Liveboards, Answers and Models. + * Opens the unpublishing modal. Appears as a child action to + * the **Publish** action if the object is already published. + * @example + * ```js + * hiddenActions: [Action.Unpublish] + * disabledActions: [Action.Unpublish] + * ``` + * @version SDK: 1.45.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Action["Unpublish"] = "unpublish"; + /** + * The **Parameterize** action for Tables and Connections. + * Opens the parameterization modal. + * @example + * ```js + * hiddenActions: [Action.Parameterize] + * disabledActions: [Action.Parameterize] + * ``` + * @version SDK: 1.45.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Action["Parameterize"] = "parameterise"; + /** + * The **Move to Group** menu action on a Liveboard. + * Allows moving a visualization to a different group. + * @example + * ```js + * disabledActions: [Action.MoveToGroup] + * ``` + * @version SDK: 1.44.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Action["MoveToGroup"] = "moveToGroup"; + /** + * The **Move out of Group** menu action on a Liveboard. + * Allows moving a visualization out of a group. + * @example + * ```js + * disabledActions: [Action.MoveOutOfGroup] + * ``` + * @version SDK: 1.44.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Action["MoveOutOfGroup"] = "moveOutOfGroup"; + /** + * The **Create Group** menu action on a Liveboard. + * Allows creating a new group. + * @example + * ```js + * disabledActions: [Action.CreateGroup] + * ``` + * @version SDK: 1.44.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Action["CreateGroup"] = "createGroup"; + /** + * The **Ungroup Liveboard Group** menu action on a Liveboard. + * Allows ungrouping a liveboard group. + * @example + * ```js + * disabledActions: [Action.UngroupLiveboardGroup] + * ``` + * @version SDK: 1.44.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Action["UngroupLiveboardGroup"] = "ungroupLiveboardGroup"; + /** + * Controls visibility of the sidebar header (title and toggle button) + * in the Spotter past conversations sidebar. + * @example + * ```js + * hiddenActions: [Action.SpotterSidebarHeader] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterSidebarHeader"] = "spotterSidebarHeader"; + /** + * Controls visibility of the sidebar footer (documentation link) + * in the Spotter past conversations sidebar. + * @example + * ```js + * hiddenActions: [Action.SpotterSidebarFooter] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterSidebarFooter"] = "spotterSidebarFooter"; + /** + * Controls visibility and disable state of the sidebar toggle/expand button + * in the Spotter past conversations sidebar. + * @example + * ```js + * disabledActions: [Action.SpotterSidebarToggle] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterSidebarToggle"] = "spotterSidebarToggle"; + /** + * Controls visibility and disable state of the "New Chat" button + * in the Spotter past conversations sidebar. + * @example + * ```js + * disabledActions: [Action.SpotterNewChat] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterNewChat"] = "spotterNewChat"; + /** + * Controls visibility of the past conversation banner alert + * in the Spotter interface. + * @example + * ```js + * hiddenActions: [Action.SpotterPastChatBanner] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterPastChatBanner"] = "spotterPastChatBanner"; + /** + * Controls visibility and disable state of the conversation edit menu + * (three-dot menu) in the Spotter past conversations sidebar. + * @example + * ```js + * disabledActions: [Action.SpotterChatMenu] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterChatMenu"] = "spotterChatMenu"; + /** + * Controls visibility and disable state of the rename action + * in the Spotter conversation edit menu. + * @example + * ```js + * disabledActions: [Action.SpotterChatRename] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterChatRename"] = "spotterChatRename"; + /** + * Controls visibility and disable state of the delete action + * in the Spotter conversation edit menu. + * @example + * ```js + * disabledActions: [Action.SpotterChatDelete] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterChatDelete"] = "spotterChatDelete"; + /** + * Controls visibility and disable state of the documentation/best practices + * link in the Spotter sidebar footer. + * @example + * ```js + * disabledActions: [Action.SpotterDocs] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterDocs"] = "spotterDocs"; + /** + * Controls visibility and disable state of the connector resources + * section in the Spotter chat interface. + * @example + * ```js + * hiddenActions: [Action.SpotterChatConnectorResources] + * disabledActions: [Action.SpotterChatConnectorResources] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot Cloud: 26.5.0.cl + */ + Action["SpotterChatConnectorResources"] = "spotterChatConnectorResources"; + /** + * Controls visibility and disable state of the connectors + * in the Spotter chat interface. + * @example + * ```js + * hiddenActions: [Action.SpotterChatConnectors] + * disabledActions: [Action.SpotterChatConnectors] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot Cloud: 26.5.0.cl + */ + Action["SpotterChatConnectors"] = "spotterChatConnectors"; + /** + * Controls visibility and disable state of the mode switcher + * in the Spotter chat interface. + * @example + * ```js + * hiddenActions: [Action.SpotterChatModeSwitcher] + * disabledActions: [Action.SpotterChatModeSwitcher] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot Cloud: 26.5.0.cl + */ + Action["SpotterChatModeSwitcher"] = "spotterChatModeSwitcher"; + /** + * The **Include current period** checkbox for date filters. + * Controls the visibility and availability of the option to include + * the current time period in filter results. + * @example + * ```js + * hiddenActions: [Action.IncludeCurrentPeriod] + * disabledActions: [Action.IncludeCurrentPeriod] + * ``` + * @version SDK: 1.45.0 | ThoughtSpot: 26.4.0.cl + */ + Action["IncludeCurrentPeriod"] = "includeCurrentPeriod"; + /** + * The **Send Test Email** button in the Liveboard schedule modal. + * Allows sending a test schedule email to self or all recipients. + * Requires `isSendNowLiveboardSchedulingEnabled` to be enabled. + * @example + * ```js + * disabledActions: [Action.SendTestScheduleEmail] + * hiddenActions: [Action.SendTestScheduleEmail] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot Cloud: 26.5.0.cl + */ + Action["SendTestScheduleEmail"] = "sendTestScheduleEmail"; + /** + * The thumbs up/down feedback buttons in the SpotterViz panel. + * Visible by default. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * hiddenActions: [Action.SpotterVizFeedback] + * disabledActions: [Action.SpotterVizFeedback] + * ``` + */ + Action["SpotterVizFeedback"] = "spotterVizFeedback"; + /** + * The version restore button on checkpoint cards in the SpotterViz panel. + * Visible by default. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * hiddenActions: [Action.SpotterVizCheckpointRestore] + * disabledActions: [Action.SpotterVizCheckpointRestore] + * ``` + */ + Action["SpotterVizCheckpointRestore"] = "spotterVizCheckpointRestore"; + /** + * The **SpotterViz** button in the top edit header. + * Visible by default. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * hiddenActions: [Action.SpotterViz] + * disabledActions: [Action.SpotterViz] + * ``` + */ + Action["SpotterViz"] = "spotterViz"; + /** + * Clears browser cache and fetches new data for liveboard ChartViz Containers. + * Requires `enableLiveboardDataCache` to be enabled. + * @example + * ```js + * disabledActions: [Action.RefreshLiveboardBrowserCache] + * hiddenActions: [Action.RefreshLiveboardBrowserCache] + * ``` + * @version SDK: 1.49.0 | ThoughtSpot Cloud: 26.6.0.cl + */ + Action["RefreshLiveboardBrowserCache"] = "refreshLiveboardBrowserCache"; +})(Action || (Action = {})); +var PrefetchFeatures; +(function (PrefetchFeatures) { + PrefetchFeatures["FullApp"] = "FullApp"; + PrefetchFeatures["SearchEmbed"] = "SearchEmbed"; + PrefetchFeatures["LiveboardEmbed"] = "LiveboardEmbed"; + PrefetchFeatures["VizEmbed"] = "VizEmbed"; +})(PrefetchFeatures || (PrefetchFeatures = {})); +/** + * Enum for options to change context trigger. + * The `BOTH_CLICKS` option is available from 10.8.0.cl. + */ +var ContextMenuTriggerOptions; +(function (ContextMenuTriggerOptions) { + ContextMenuTriggerOptions["LEFT_CLICK"] = "left-click"; + ContextMenuTriggerOptions["RIGHT_CLICK"] = "right-click"; + ContextMenuTriggerOptions["BOTH_CLICKS"] = "both-clicks"; +})(ContextMenuTriggerOptions || (ContextMenuTriggerOptions = {})); +/** + * Enum options to show custom actions at different + * positions in the application. + */ +var CustomActionsPosition; +(function (CustomActionsPosition) { + /** + * Shows the action as a primary button + * in the toolbar area of the embed. + */ + CustomActionsPosition["PRIMARY"] = "PRIMARY"; + /** + * Shows the action inside the "More" menu + * (three-dot overflow menu). + */ + CustomActionsPosition["MENU"] = "MENU"; + /** + * Shows the action in the right-click + * context menu. Only supported for + * {@link CustomActionTarget.VIZ}, + * {@link CustomActionTarget.ANSWER}, and + * {@link CustomActionTarget.SPOTTER} targets. + */ + CustomActionsPosition["CONTEXTMENU"] = "CONTEXTMENU"; +})(CustomActionsPosition || (CustomActionsPosition = {})); +/** + * Enum options to mention the target of the code-based custom action. + * The target determines which type of ThoughtSpot object the action is + * associated with, and also controls which positions and scoping options + * are available. + */ +var CustomActionTarget; +(function (CustomActionTarget) { + /** + * Action applies at the Liveboard level. + * Supported positions: + * {@link CustomActionsPosition.PRIMARY}, + * {@link CustomActionsPosition.MENU}. + * Can be scoped with + * `metadataIds.liveboardIds`, + * `orgIds`, and `groupIds`. + */ + CustomActionTarget["LIVEBOARD"] = "LIVEBOARD"; + /** + * Action applies to individual + * visualizations (charts/tables). + * Supported positions: + * {@link CustomActionsPosition.PRIMARY}, + * {@link CustomActionsPosition.MENU}, + * {@link CustomActionsPosition.CONTEXTMENU}. + * Can be scoped with `metadataIds` + * (answerIds, liveboardIds, vizIds), + * `dataModelIds` (modelIds, + * modelColumnNames), `orgIds`, + * and `groupIds`. + */ + CustomActionTarget["VIZ"] = "VIZ"; + /** + * Action applies to saved or unsaved + * Answers. Supported positions: + * {@link CustomActionsPosition.PRIMARY}, + * {@link CustomActionsPosition.MENU}, + * {@link CustomActionsPosition.CONTEXTMENU}. + * Can be scoped with + * `metadataIds.answerIds`, + * `dataModelIds` (modelIds, + * modelColumnNames), `orgIds`, + * and `groupIds`. + */ + CustomActionTarget["ANSWER"] = "ANSWER"; + /** + * Action applies to Spotter + * (AI-powered search). + * Supported positions: + * {@link CustomActionsPosition.MENU}, + * {@link CustomActionsPosition.CONTEXTMENU}. + * Can be scoped with + * `dataModelIds.modelIds`, + * `orgIds`, and `groupIds`. + */ + CustomActionTarget["SPOTTER"] = "SPOTTER"; +})(CustomActionTarget || (CustomActionTarget = {})); +/** + * Enum options to show or suppress Visual Embed SDK and + * ThoughtSpot application logs in the console output. + * This attribute doesn't support suppressing + * browser warnings or errors. + */ +var LogLevel; +(function (LogLevel) { + /** + * No application or SDK-related logs will be logged + * in the console output. + * @example + * ```js + * init({ + * ... //other embed view config, + * logLevel: LogLevel.SILENT, + * }) + * ``` + * @version SDK: 1.26.7 | ThoughtSpot Cloud: 9.10.0.cl + */ + LogLevel["SILENT"] = "SILENT"; + /** + * Log only errors in the console output. + * @example + * ```js + * init({ + * ... //other embed view config, + * logLevel: LogLevel.ERROR, + * }) + * ``` + * @version SDK: 1.26.7 | ThoughtSpot Cloud: 9.10.0.cl + */ + LogLevel["ERROR"] = "ERROR"; + /** + * Log only warnings and errors in the console output. + * @example + * ```js + * init({ + * ... //other embed view config, + * logLevel: LogLevel.WARN, + * }) + * ``` + * @version SDK: 1.26.7 | ThoughtSpot Cloud: 9.10.0.cl + */ + LogLevel["WARN"] = "WARN"; + /** + * Log only the information alerts, warnings, and errors + * in the console output. + * @example + * ```js + * init({ + * ... //other embed view config, + * logLevel: LogLevel.INFO, + * }) + * ``` + * @version SDK: 1.26.7 | ThoughtSpot Cloud: 9.10.0.cl + */ + LogLevel["INFO"] = "INFO"; + /** + * Log debug messages, warnings, information alerts, + * and errors in the console output. + * @example + * ```js + * init({ + * ... //other embed view config, + * logLevel: LogLevel.DEBUG, + * }) + * ``` + * @version SDK: 1.26.7 | ThoughtSpot Cloud: 9.10.0.cl + */ + LogLevel["DEBUG"] = "DEBUG"; + /** + * All logs will be logged in the browser console. + * @example + * ```js + * init({ + * ... //other embed view config, + * logLevel: LogLevel.TRACE, + * }) + * ``` + * @version SDK: 1.26.7 | ThoughtSpot Cloud: 9.10.0.cl + */ + LogLevel["TRACE"] = "TRACE"; +})(LogLevel || (LogLevel = {})); +/** + * Error types emitted by embedded components. + * + * These enum values categorize different types of errors that can occur during + * the lifecycle of an embedded ThoughtSpot component. + * Use {@link EmbedErrorDetailsEvent} and {@link EmbedErrorCodes} to handle specific errors. + * @version SDK: 1.44.2 | ThoughtSpot: 26.2.0.cl + * @group Error Handling + * + * @example + * Handle specific error types + * ```js + * embed.on(EmbedEvent.Error, (error) => { + * switch (error.errorType) { + * case ErrorDetailsTypes.API: + * console.error('API error:', error.message); + * break; + * case ErrorDetailsTypes.VALIDATION_ERROR: + * console.error('Validation error:', error.message); + * break; + * case ErrorDetailsTypes.NETWORK: + * console.error('Network error:', error.message); + * break; + * default: + * console.error('Unknown error:', error); + * } + * }); + * ``` + */ +var ErrorDetailsTypes; +(function (ErrorDetailsTypes) { + /** API call failure */ + ErrorDetailsTypes["API"] = "API"; + /** General validation error */ + ErrorDetailsTypes["VALIDATION_ERROR"] = "VALIDATION_ERROR"; + /** Network connectivity or request error */ + ErrorDetailsTypes["NETWORK"] = "NETWORK"; +})(ErrorDetailsTypes || (ErrorDetailsTypes = {})); +/** + * Error codes for identifying specific issues in embedded ThoughtSpot components. Use + * {@link EmbedErrorDetailsEvent} and {@link ErrorDetailsTypes} codes for precise error + * handling and debugging. + * + * @version SDK: 1.44.2 | ThoughtSpot: 26.2.0.cl + * @group Error Handling + + * @example + * Handle specific error codes in the error event handler + * ```js + * embed.on(EmbedEvent.Error, (error) => { + * switch (error.code) { + * case EmbedErrorCodes.WORKSHEET_ID_NOT_FOUND: + * console.error('Worksheet ID not found:', error.message); + * break; + * case EmbedErrorCodes.LIVEBOARD_ID_MISSING: + * console.error('Liveboard ID is missing:', error.message); + * break; + * case EmbedErrorCodes.CONFLICTING_ACTIONS_CONFIG: + * console.error('Conflicting actions configuration:', error.message); + * break; + * case EmbedErrorCodes.CONFLICTING_TABS_CONFIG: + * console.error('Conflicting tabs configuration:', error.message); + * break; + * default: + * console.error('Unknown error:', error); + * } + * }); + * ``` + * */ +var EmbedErrorCodes; +(function (EmbedErrorCodes) { + /** Worksheet ID not found or does not exist */ + EmbedErrorCodes["WORKSHEET_ID_NOT_FOUND"] = "WORKSHEET_ID_NOT_FOUND"; + /** Required Liveboard ID is missing from configuration */ + EmbedErrorCodes["LIVEBOARD_ID_MISSING"] = "LIVEBOARD_ID_MISSING"; + /** Conflicting action configuration detected */ + EmbedErrorCodes["CONFLICTING_ACTIONS_CONFIG"] = "CONFLICTING_ACTIONS_CONFIG"; + /** Conflicting tab configuration detected */ + EmbedErrorCodes["CONFLICTING_TABS_CONFIG"] = "CONFLICTING_TABS_CONFIG"; + /** Error during component initialization */ + EmbedErrorCodes["INIT_ERROR"] = "INIT_ERROR"; + /** Network connectivity or request error */ + EmbedErrorCodes["NETWORK_ERROR"] = "NETWORK_ERROR"; + /** Custom action validation failed */ + EmbedErrorCodes["CUSTOM_ACTION_VALIDATION"] = "CUSTOM_ACTION_VALIDATION"; + /** Authentication/login failed */ + EmbedErrorCodes["LOGIN_FAILED"] = "LOGIN_FAILED"; + /** Render method was not called before attempting to use the component */ + EmbedErrorCodes["RENDER_NOT_CALLED"] = "RENDER_NOT_CALLED"; + /** Host event type is undefined or invalid */ + EmbedErrorCodes["HOST_EVENT_TYPE_UNDEFINED"] = "HOST_EVENT_TYPE_UNDEFINED"; + /** Error parsing api intercept body */ + EmbedErrorCodes["PARSING_API_INTERCEPT_BODY_ERROR"] = "PARSING_API_INTERCEPT_BODY_ERROR"; + /** Failed to update embed parameters during pre-render */ + EmbedErrorCodes["UPDATE_PARAMS_FAILED"] = "UPDATE_PARAMS_FAILED"; + /** Invalid URL provided in configuration */ + EmbedErrorCodes["INVALID_URL"] = "INVALID_URL"; + /** Host event payload validation failed */ + EmbedErrorCodes["HOST_EVENT_VALIDATION"] = "HOST_EVENT_VALIDATION"; + /** UpdateFilters payload is invalid - missing or malformed filter/filters */ + EmbedErrorCodes["UPDATEFILTERS_INVALID_PAYLOAD"] = "UPDATEFILTERS_INVALID_PAYLOAD"; + /** DrillDown payload is invalid - missing or malformed points */ + EmbedErrorCodes["DRILLDOWN_INVALID_PAYLOAD"] = "DRILLDOWN_INVALID_PAYLOAD"; +})(EmbedErrorCodes || (EmbedErrorCodes = {})); +/** + * Context types for specifying the page context when triggering host events. + * Used as the third parameter in the `trigger` method to help ThoughtSpot + * understand the current page context for better event handling. + * + * @example + * ```js + * import { HostEvent, ContextType } from '@thoughtspot/visual-embed-sdk'; + * + * // Trigger an event with specific context + * embed.trigger(HostEvent.Pin, { vizId: "123", liveboardId: "456" }, ContextType.Search); + * ``` + * + * @version SDK: 1.45.2 | ThoughtSpot: 26.3.0.cl + * @group Events + */ +var ContextType; +(function (ContextType) { + /** + * Search answer context for search page or edit viz dialog on liveboard page. + */ + ContextType["Search"] = "search-answer"; + /** + * Liveboard context for liveboard page. + */ + ContextType["Liveboard"] = "liveboard"; + /** + * Answer context for explore modal/page on liveboard page. + */ + ContextType["Answer"] = "answer"; + /** + * Spotter context for spotter modal/page. + */ + ContextType["Spotter"] = "spotter"; + /** + * Other context for any other generic page. + */ + ContextType["Other"] = "other"; +})(ContextType || (ContextType = {})); +/** + * Enum for the type of API intercepted + */ +var InterceptedApiType; +(function (InterceptedApiType) { + /** + * The apis that are use to get the data for the embed + */ + InterceptedApiType["AnswerData"] = "AnswerData"; + /** + * This will intercept all the apis + */ + InterceptedApiType["ALL"] = "ALL"; + /** + * The apis that are use to get the data for the liveboard + */ + InterceptedApiType["LiveboardData"] = "LiveboardData"; +})(InterceptedApiType || (InterceptedApiType = {})); +/** + * Data label filter operators + * @group Visual Overrides + */ +var DataLabelFilterOperator; +(function (DataLabelFilterOperator) { + /** Greater than */ + DataLabelFilterOperator["GreaterThan"] = "GREATER_THAN"; + /** Less than */ + DataLabelFilterOperator["LessThan"] = "LESS_THAN"; + /** Greater than or equal to */ + DataLabelFilterOperator["GreaterThanOrEqualTo"] = "GREATER_THAN_OR_EQUAL_TO"; + /** Less than or equal to */ + DataLabelFilterOperator["LessThanOrEqualTo"] = "LESS_THAN_OR_EQUAL_TO"; + /** Equal to */ + DataLabelFilterOperator["EqualTo"] = "EQUAL_TO"; + /** Not equal to */ + DataLabelFilterOperator["NotEqualTo"] = "NOT_EQUAL_TO"; +})(DataLabelFilterOperator || (DataLabelFilterOperator = {})); +/** + * Conditional formatting operators + * @group Visual Overrides + */ +var ConditionalFormattingOperator; +(function (ConditionalFormattingOperator) { + /** Is equal to */ + ConditionalFormattingOperator["Is"] = "IS"; + /** Is not equal to */ + ConditionalFormattingOperator["IsNot"] = "IS_NOT"; + /** Contains */ + ConditionalFormattingOperator["Contains"] = "CONTAINS"; + /** Does not contain */ + ConditionalFormattingOperator["DoesNotContain"] = "DOES_NOT_CONTAIN"; + /** Starts with */ + ConditionalFormattingOperator["StartsWith"] = "STARTS_WITH"; + /** Ends with */ + ConditionalFormattingOperator["EndsWith"] = "ENDS_WITH"; + /** Greater than */ + ConditionalFormattingOperator["GreaterThan"] = "GREATER_THAN"; + /** Less than */ + ConditionalFormattingOperator["LessThan"] = "LESS_THAN"; + /** Greater than or equal to */ + ConditionalFormattingOperator["GreaterThanEqualTo"] = "GREATER_THAN_EQUAL_TO"; + /** Less than or equal to */ + ConditionalFormattingOperator["LessThanEqualTo"] = "LESS_THAN_EQUAL_TO"; + /** Equal to */ + ConditionalFormattingOperator["EqualTo"] = "EQUAL_TO"; + /** Not equal to */ + ConditionalFormattingOperator["NotEqualTo"] = "NOT_EQUAL_TO"; + /** Is between */ + ConditionalFormattingOperator["IsBetween"] = "IS_BETWEEN"; + /** Is null */ + ConditionalFormattingOperator["IsNull"] = "IS_NULL"; + /** Is not null */ + ConditionalFormattingOperator["IsNotNull"] = "IS_NOT_NULL"; +})(ConditionalFormattingOperator || (ConditionalFormattingOperator = {})); +/** + * Background format types for conditional formatting + * @group Visual Overrides + */ +var BackgroundFormatType; +(function (BackgroundFormatType) { + /** Solid color background */ + BackgroundFormatType["Solid"] = "SOLID"; + /** Gradient background */ + BackgroundFormatType["Gradient"] = "GRADIENT"; +})(BackgroundFormatType || (BackgroundFormatType = {})); +/** + * Comparison types for conditional formatting + * @group Visual Overrides + */ +var ConditionalFormattingComparisonType; +(function (ConditionalFormattingComparisonType) { + /** Value-based comparison */ + ConditionalFormattingComparisonType["ValueBased"] = "VALUE_BASED"; + /** Column-based comparison */ + ConditionalFormattingComparisonType["ColumnBased"] = "COLUMN_BASED"; + /** Parameter-based comparison */ + ConditionalFormattingComparisonType["ParameterBased"] = "PARAMETER_BASED"; +})(ConditionalFormattingComparisonType || (ConditionalFormattingComparisonType = {})); +/** + * Legend position options + * @group Visual Overrides + */ +var LegendPosition; +(function (LegendPosition) { + /** Position legend at the top */ + LegendPosition["Top"] = "top"; + /** Position legend at the bottom */ + LegendPosition["Bottom"] = "bottom"; + /** Position legend on the left */ + LegendPosition["Left"] = "left"; + /** Position legend on the right */ + LegendPosition["Right"] = "right"; +})(LegendPosition || (LegendPosition = {})); +/** + * Table theme options + * @group Visual Overrides + */ +var TableTheme; +(function (TableTheme) { + /** Outline theme */ + TableTheme["Outline"] = "OUTLINE"; + /** Row theme */ + TableTheme["Row"] = "ROW"; + /** Zebra theme */ + TableTheme["Zebra"] = "ZEBRA"; +})(TableTheme || (TableTheme = {})); +/** + * Table content density options + * @group Visual Overrides + */ +var TableContentDensity; +(function (TableContentDensity) { + /** Regular density */ + TableContentDensity["Regular"] = "REGULAR"; + /** Compact density */ + TableContentDensity["Compact"] = "COMPACT"; +})(TableContentDensity || (TableContentDensity = {})); + +var version="1.48.0";var packageInfo = {version:version}; + +const logFunctions = { + [LogLevel.SILENT]: () => undefined, + [LogLevel.ERROR]: console.error, + [LogLevel.WARN]: console.warn, + [LogLevel.INFO]: console.info, + [LogLevel.DEBUG]: console.debug, + [LogLevel.TRACE]: console.trace, +}; +let globalLogLevelOverride = LogLevel.TRACE; +const setGlobalLogLevelOverride = (logLevel) => { + globalLogLevelOverride = logLevel; +}; +const logLevelToNumber = { + [LogLevel.SILENT]: 0, + [LogLevel.ERROR]: 1, + [LogLevel.WARN]: 2, + [LogLevel.INFO]: 3, + [LogLevel.DEBUG]: 4, + [LogLevel.TRACE]: 5, +}; +const compareLogLevels = (logLevel1, logLevel2) => { + const logLevel1Index = logLevelToNumber[logLevel1]; + const logLevel2Index = logLevelToNumber[logLevel2]; + return logLevel1Index - logLevel2Index; +}; +class Logger { + constructor() { + this.logLevel = LogLevel.ERROR; + this.setLogLevel = (newLogLevel) => { + this.logLevel = newLogLevel; + }; + this.getLogLevel = () => this.logLevel; + } + canLog(logLevel) { + if (logLevel === LogLevel.SILENT) + return false; + if (globalLogLevelOverride !== undefined) { + return compareLogLevels(globalLogLevelOverride, logLevel) >= 0; + } + return compareLogLevels(this.logLevel, logLevel) >= 0; + } + logMessages(args, logLevel) { + if (this.canLog(logLevel)) { + const logFn = logFunctions[logLevel]; + if (logFn) { + logFn(`[vesdk-${version}]`, ...args); + } + } + } + log(...args) { + this.info(args); + } + info(...args) { + this.logMessages(args, LogLevel.INFO); + } + debug(...args) { + this.logMessages(args, LogLevel.DEBUG); + } + trace(...args) { + this.logMessages(args, LogLevel.TRACE); + } + error(...args) { + this.logMessages(args, LogLevel.ERROR); + } + warn(...args) { + this.logMessages(args, LogLevel.WARN); + } +} +const logger$3 = new Logger(); + +const ERROR_MESSAGE = { + INVALID_THOUGHTSPOT_HOST: 'Error parsing ThoughtSpot host. Please provide a valid URL.', + SPOTTER_EMBED_WORKSHEED_ID_NOT_FOUND: 'Please select a Model to get started', + LIVEBOARD_VIZ_ID_VALIDATION: 'Please select a Liveboard to embed.', + TRIGGER_TIMED_OUT: 'Trigger timed-out in getting a response', + SEARCHEMBED_BETA_WRANING_MESSAGE: 'SearchEmbed is in Beta in this release.', + THIRD_PARTY_COOKIE_BLOCKED_ALERT: 'Third-party cookie access is blocked on this browser. Please allow third-party cookies for this to work properly. \nYou can use `suppressNoCookieAccessAlert` to suppress this message.', + DUPLICATE_TOKEN_ERR: 'Duplicate token. Please issue a new token every time getAuthToken callback is called. See https://developers.thoughtspot.com/docs/?pageid=embed-auth#trusted-auth-embed for more details.', + SDK_NOT_INITIALIZED: 'SDK not initialized', + SESSION_INFO_FAILED: 'Failed to get session information', + INVALID_TOKEN_ERROR: 'Received invalid token from getAuthToken callback or authToken endpoint.', + INVALID_TOKEN_TYPE_ERROR: 'Expected getAuthToken to return a string, but received a {invalidType}.', + MIXPANEL_TOKEN_NOT_FOUND: 'Mixpanel token not found in session info', + PRERENDER_ID_MISSING: 'PreRender ID is required for preRender', + SYNC_STYLE_CALLED_BEFORE_RENDER: 'PreRender should be called before using syncPreRenderStyle', + CSP_VIOLATION_ALERT: 'CSP violation detected. Please check the console errors for more details.', + CSP_FRAME_HOST_VIOLATION_LOG_MESSAGE: 'Please set up CSP correctly for the application to start working. For more information, see https://developers.thoughtspot.com/docs/security-settings#csp-viz-embed-hosts. \n If the issue persists, refer to https://developers.thoughtspot.com/docs/security-settings#csp-viz-embed-hosts', + MISSING_REPORTING_OBSERVER: 'ReportingObserver not supported', + RENDER_CALLED_BEFORE_INIT: 'Looks like render was called before calling init, the render won\'t start until init is called.\nFor more info check\n1. https://developers.thoughtspot.com/docs/Function_init#_init\n2.https://developers.thoughtspot.com/docs/getting-started#initSdk', + SPOTTER_AGENT_NOT_INITIALIZED: 'SpotterAgent not initialized', + OFFLINE_WARNING: 'Network not Detected. Embed is offline. Please reconnect and refresh', + INIT_SDK_REQUIRED: 'You need to init the ThoughtSpot SDK module first', + CONFLICTING_ACTIONS_CONFIG: 'You cannot have both hidden actions and visible actions', + CONFLICTING_TABS_CONFIG: 'You cannot have both hidden Tabs and visible Tabs', + RENDER_BEFORE_EVENTS_REQUIRED: 'Please call render before triggering events', + HOST_EVENT_TYPE_UNDEFINED: 'Host event type is undefined', + LOGIN_FAILED: 'Login failed', + ERROR_PARSING_API_INTERCEPT_BODY: 'Error parsing api intercept body', + SSR_ENVIRONMENT_ERROR: 'SSR environment detected. This function cannot be called in SSR environment.', + UPDATE_PARAMS_FAILED: 'Failed to update embed parameters', + INVALID_SPOTTER_DOCUMENTATION_URL: 'Invalid spotterDocumentationUrl. Please provide a valid http or https URL.', + UPDATEFILTERS_INVALID_PAYLOAD: 'UpdateFilters requires a valid filter or filters array. Expected: { filter: { column, oper, values } } or { filters: [{ column, oper, values }, ...] }', + DRILLDOWN_INVALID_PAYLOAD: 'DrillDown requires a valid points object. Expected: { points: { clickedPoint?, selectedPoints? }, autoDrillDown?, vizId? }', +}; +const CUSTOM_ACTIONS_ERROR_MESSAGE = { + INVALID_ACTION_OBJECT: 'Custom Action Validation Error: Invalid action object provided', + MISSING_REQUIRED_FIELDS: (id, missingFields) => `Custom Action Validation Error for '${id}': Missing required fields: ${missingFields.join(', ')}`, + UNSUPPORTED_TARGET: (id, targetType) => `Custom Action Validation Error for '${id}': Target type '${targetType}' is not supported`, + INVALID_POSITION: (position, targetType, supportedPositions) => `Position '${position}' is not supported for ${targetType.toLowerCase()}-level custom actions. Supported positions: ${supportedPositions}`, + INVALID_METADATA_IDS: (targetType, invalidIds, supportedIds) => `Invalid metadata IDs for ${targetType.toLowerCase()}-level custom actions: ${invalidIds.join(', ')}. Supported metadata IDs: ${supportedIds}`, + INVALID_DATA_MODEL_IDS: (targetType, invalidIds, supportedIds) => `Invalid data model IDs for ${targetType.toLowerCase()}-level custom actions: ${invalidIds.join(', ')}. Supported data model IDs: ${supportedIds}`, + INVALID_FIELDS: (targetType, invalidFields, supportedFields) => `Invalid fields for ${targetType.toLowerCase()}-level custom actions: ${invalidFields.join(', ')}. Supported fields: ${supportedFields}`, + DUPLICATE_IDS: (id, duplicateNames, keptName) => `Duplicate custom action ID '${id}' found. Actions with names '${duplicateNames.join("', '")}' will be ignored. Keeping '${keptName}'.`, +}; + +/** + * Copyright (c) 2023 + * + * Common utility functions for ThoughtSpot Visual Embed SDK + * @summary Utils + * @author Ayon Ghosh + */ +/** + * Construct a runtime filters query string from the given filters. + * Refer to the following docs for more details on runtime filter syntax: + * https://cloud-docs.thoughtspot.com/admin/ts-cloud/apply-runtime-filter.html + * https://cloud-docs.thoughtspot.com/admin/ts-cloud/runtime-filter-operators.html + * @param runtimeFilters + */ +const getFilterQuery = (runtimeFilters) => { + if (runtimeFilters && runtimeFilters.length) { + const filters = runtimeFilters.map((filter, valueIndex) => { + const index = valueIndex + 1; + const filterExpr = []; + filterExpr.push(`col${index}=${encodeURIComponent(filter.columnName)}`); + filterExpr.push(`op${index}=${filter.operator}`); + filterExpr.push(filter.values.map((value) => { + const encodedValue = typeof value === 'bigint' ? value.toString() : value; + return `val${index}=${encodeURIComponent(String(encodedValue))}`; + }).join('&')); + return filterExpr.join('&'); + }); + return `${filters.join('&')}`; + } + return null; +}; +/** + * Construct a runtime parameter override query string from the given option. + * @param runtimeParameters + */ +const getRuntimeParameters = (runtimeParameters) => { + if (runtimeParameters && runtimeParameters.length) { + const params = runtimeParameters.map((param, valueIndex) => { + const index = valueIndex + 1; + const filterExpr = []; + filterExpr.push(`param${index}=${encodeURIComponent(param.name)}`); + filterExpr.push(`paramVal${index}=${encodeURIComponent(param.value)}`); + return filterExpr.join('&'); + }); + return `${params.join('&')}`; + } + return null; +}; +/** + * Convert a value to a string representation to be sent as a query + * parameter to the ThoughtSpot app. + * @param value Any parameter value + */ +const serializeParam = (value) => { + // do not serialize primitive types + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + return value; + } + return JSON.stringify(value); +}; +/** + * Convert a value to a string: + * in case of an array, we convert it to CSV. + * in case of any other type, we directly return the value. + * @param value + */ +const paramToString = (value) => (Array.isArray(value) ? value.join(',') : value); +/** + * Return a query param string composed from the given params object + * @param queryParams + * @param shouldSerializeParamValues + */ +const getQueryParamString = (queryParams, shouldSerializeParamValues = false) => { + const qp = []; + const params = Object.keys(queryParams); + params.forEach((key) => { + const val = queryParams[key]; + if (val !== undefined) { + const serializedValue = shouldSerializeParamValues + ? serializeParam(val) + : paramToString(val); + qp.push(`${key}=${serializedValue}`); + } + }); + if (qp.length) { + return qp.join('&'); + } + return null; +}; +/** + * Get a string representation of a dimension value in CSS + * If numeric, it is considered in pixels. + * @param value + */ +const getCssDimension = (value) => { + if (typeof value === 'number') { + return `${value}px`; + } + return value; +}; +/** + * Validates if a string is a valid CSS margin value. + * @param value - The string to validate + * @returns true if the value is a valid CSS margin value, false otherwise + */ +const isValidCssMargin = (value) => { + if (isUndefined(value)) { + return false; + } + if (typeof value !== 'string') { + logger$3.error('Please provide a valid lazyLoadingMargin value (e.g., "10px")'); + return false; + } + // This pattern allows for an optional negative sign, and numbers + // that can be integers or decimals (including leading dot). + const cssUnitPattern = /^-?(\d+(\.\d*)?|\.\d+)(px|em|rem|%|vh|vw)$/i; + const parts = value.trim().split(/\s+/); + if (parts.length > 4) { + logger$3.error('Please provide a valid lazyLoadingMargin value (e.g., "10px")'); + return false; + } + const isValid = parts.every(part => { + const trimmedPart = part.trim(); + return trimmedPart.toLowerCase() === 'auto' || trimmedPart === '0' || cssUnitPattern.test(trimmedPart); + }); + if (!isValid) { + logger$3.error('Please provide a valid lazyLoadingMargin value (e.g., "10px")'); + return false; + } + return true; +}; +const getSSOMarker = (markerId) => { + const encStringToAppend = encodeURIComponent(markerId); + return `tsSSOMarker=${encStringToAppend}`; +}; +/** + * Append a string to a URL's hash fragment + * @param url A URL + * @param stringToAppend The string to append to the URL hash + */ +const appendToUrlHash = (url, stringToAppend) => { + let outputUrl = url; + const encStringToAppend = encodeURIComponent(stringToAppend); + const marker = `tsSSOMarker=${encStringToAppend}`; + let splitAdder = ''; + if (url.indexOf('#') >= 0) { + // If second half of hash contains a '?' already add a '&' instead of + // '?' which appends to query params. + splitAdder = url.split('#')[1].indexOf('?') >= 0 ? '&' : '?'; + } + else { + splitAdder = '#?'; + } + outputUrl = `${outputUrl}${splitAdder}${marker}`; + return outputUrl; +}; +/** + * + * @param url + * @param stringToAppend + * @param path + */ +function getRedirectUrl(url, stringToAppend, path = '') { + const targetUrl = path ? new URL(path, window.location.origin).href : url; + return appendToUrlHash(targetUrl, stringToAppend); +} +const getEncodedQueryParamsString = (queryString) => { + if (!queryString) { + return queryString; + } + return btoa(queryString).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); +}; +const getOffsetTop = (element) => { + const rect = element.getBoundingClientRect(); + return rect.top + window.scrollY; +}; +const embedEventStatus = { + START: 'start', + END: 'end', +}; +const setAttributes = (element, attributes) => { + Object.keys(attributes).forEach((key) => { + element.setAttribute(key, attributes[key].toString()); + }); +}; +const isCloudRelease = (version) => version.endsWith('.cl'); +/* For Search Embed: ReleaseVersionInBeta */ +const checkReleaseVersionInBeta = (releaseVersion, suppressBetaWarning) => { + if (releaseVersion !== '' && !isCloudRelease(releaseVersion)) { + const splittedReleaseVersion = releaseVersion.split('.'); + const majorVersion = Number(splittedReleaseVersion[0]); + const isBetaVersion = majorVersion < 8; + return !suppressBetaWarning && isBetaVersion; + } + return false; +}; +const getCustomisations = (embedConfig, viewConfig) => { + const customizationsFromViewConfig = viewConfig.customizations; + const customizationsFromEmbedConfig = embedConfig.customizations + || embedConfig.customisations; + const customizations = { + style: { + ...customizationsFromEmbedConfig?.style, + ...customizationsFromViewConfig?.style, + customCSS: { + ...customizationsFromEmbedConfig?.style?.customCSS, + ...customizationsFromViewConfig?.style?.customCSS, + }, + customCSSUrl: customizationsFromViewConfig?.style?.customCSSUrl + || customizationsFromEmbedConfig?.style?.customCSSUrl, + }, + content: { + ...customizationsFromEmbedConfig?.content, + ...customizationsFromViewConfig?.content, + }, + }; + return customizations; +}; +const getRuntimeFilters = (runtimefilters) => getFilterQuery(runtimefilters || []); +/** + * Gets a reference to the DOM node given + * a selector. + * @param domSelector + */ +function getDOMNode(domSelector) { + return typeof domSelector === 'string' ? document.querySelector(domSelector) : domSelector; +} +const deepMerge = (target, source) => merge(target, source); +const getOperationNameFromQuery = (query) => { + const regex = /(?:query|mutation)\s+(\w+)/; + const matches = query.match(regex); + return matches?.[1]; +}; +/** + * + * @param obj + */ +function removeTypename(obj) { + if (!obj || typeof obj !== 'object') + return obj; + for (const key in obj) { + if (key === '__typename') { + delete obj[key]; + } + else if (typeof obj[key] === 'object') { + removeTypename(obj[key]); + } + } + return obj; +} +/** + * Sets the specified style properties on an HTML element. + * @param {HTMLElement} element - The HTML element to which the styles should be applied. + * @param {Partial} styleProperties - An object containing style + * property names and their values. + * @example + * // Apply styles to an element + * const element = document.getElementById('myElement'); + * const styles = { + * backgroundColor: 'red', + * fontSize: '16px', + * }; + * setStyleProperties(element, styles); + */ +const setStyleProperties = (element, styleProperties) => { + if (!element?.style) + return; + Object.keys(styleProperties).forEach((styleProperty) => { + const styleKey = styleProperty; + const value = styleProperties[styleKey]; + if (value !== undefined) { + element.style[styleKey] = value.toString(); + } + }); +}; +/** + * Removes specified style properties from an HTML element. + * @param {HTMLElement} element - The HTML element from which the styles should be removed. + * @param {string[]} styleProperties - An array of style property names to be removed. + * @example + * // Remove styles from an element + * const element = document.getElementById('myElement'); + * element.style.backgroundColor = 'red'; + * const propertiesToRemove = ['backgroundColor']; + * removeStyleProperties(element, propertiesToRemove); + */ +const removeStyleProperties = (element, styleProperties) => { + if (!element?.style) + return; + styleProperties.forEach((styleProperty) => { + element.style.removeProperty(styleProperty); + }); +}; +const isUndefined = (value) => value === undefined; +// Return if the value is a string, double or boolean. +const getTypeFromValue = (value) => { + if (typeof value === 'string') { + return ['char', 'string']; + } + if (typeof value === 'number') { + return ['double', 'double']; + } + if (typeof value === 'boolean') { + return ['boolean', 'boolean']; + } + return ['', '']; +}; +const sdkWindowKey = '_tsEmbedSDK'; +/** + * Stores a value in the global `window` object under the `_tsEmbedSDK` namespace. + * @param key - The key under which the value will be stored. + * @param value - The value to store. + * @param options - Additional options. + * @param options.ignoreIfAlreadyExists - Does not set if value for key is set. + * + * @returns The stored value. + * + * @version SDK: 1.36.2 | ThoughtSpot: * + */ +function storeValueInWindow(key, value, options = {}) { + if (isWindowUndefined()) + return value; + if (!window[sdkWindowKey]) { + window[sdkWindowKey] = {}; + } + if (options.ignoreIfAlreadyExists && key in window[sdkWindowKey]) { + return window[sdkWindowKey][key]; + } + window[sdkWindowKey][key] = value; + return value; +} +/** + * Retrieves a stored value from the global + * `window` object under the `_tsEmbedSDK` namespace. + * Returns undefined in SSR environment. + */ +const getValueFromWindow = (key) => { + if (isWindowUndefined()) + return undefined; + return window?.[sdkWindowKey]?.[key]; +}; +/** + * Check if an array includes a string value + * @param arr - The array to check + * @param key - The string to search for + * @returns boolean indicating if the string is found in the array + */ +const arrayIncludesString = (arr, key) => { + return arr.some(item => typeof item === 'string' && item === key); +}; +/** + * Check if the document is currently in fullscreen mode + */ +const isInFullscreen = () => { + return !!(document.fullscreenElement || + document.webkitFullscreenElement || + document.mozFullScreenElement || + document.msFullscreenElement); +}; +/** + * Handle Present HostEvent by entering fullscreen mode + * @param iframe The iframe element to make fullscreen + */ +const handlePresentEvent = async (iframe) => { + if (isInFullscreen()) { + return; // Already in fullscreen + } + // Browser-specific methods to enter fullscreen mode + const fullscreenMethods = [ + 'requestFullscreen', + 'webkitRequestFullscreen', + 'mozRequestFullScreen', + 'msRequestFullscreen' // IE/Edge + ]; + for (const method of fullscreenMethods) { + if (typeof iframe[method] === 'function') { + try { + const result = iframe[method](); + await Promise.resolve(result); + return; + } + catch (error) { + logger$3.warn(`Failed to enter fullscreen using ${method}:`, error); + } + } + } + logger$3.error('Fullscreen API is not supported by this browser.'); +}; +/** + * Handle ExitPresentMode EmbedEvent by exiting fullscreen mode + */ +const handleExitPresentMode = async () => { + if (!isInFullscreen()) { + return; // Not in fullscreen + } + const exitFullscreenMethods = [ + 'exitFullscreen', + 'webkitExitFullscreen', + 'mozCancelFullScreen', + 'msExitFullscreen' // IE/Edge + ]; + // Try each method until one works + for (const method of exitFullscreenMethods) { + if (typeof document[method] === 'function') { + try { + const result = document[method](); + await Promise.resolve(result); + return; + } + catch (error) { + logger$3.warn(`Failed to exit fullscreen using ${method}:`, error); + } + } + } + logger$3.warn('Exit fullscreen API is not supported by this browser.'); +}; +const calculateVisibleElementData = (element) => { + const rect = element.getBoundingClientRect(); + const windowHeight = window.innerHeight; + const windowWidth = window.innerWidth; + const frameRelativeTop = Math.max(rect.top, 0); + const frameRelativeLeft = Math.max(rect.left, 0); + const frameRelativeBottom = Math.min(windowHeight, rect.bottom); + const frameRelativeRight = Math.min(windowWidth, rect.right); + const data = { + top: Math.max(0, rect.top * -1), + height: Math.max(0, frameRelativeBottom - frameRelativeTop), + left: Math.max(0, rect.left * -1), + width: Math.max(0, frameRelativeRight - frameRelativeLeft), + }; + return data; +}; +/** + * Replaces placeholders in a template string with provided values. + * Placeholders should be in the format {key}. + * @param template - The template string with placeholders + * @param values - An object containing key-value pairs to replace placeholders + * @returns The template string with placeholders replaced + * @example + * formatTemplate('Hello {name}, you are {age} years old', { name: 'John', age: 30 }) + * // Returns: 'Hello John, you are 30 years old' + * + * formatTemplate('Expected {type}, but received {actual}', { type: 'string', actual: 'number' }) + * // Returns: 'Expected string, but received number' + */ +const formatTemplate = (template, values) => { + // This regex /\{(\w+)\}/g finds all placeholders in the format {word} + // and captures the word inside the braces for replacement. + return template.replace(/\{(\w+)\}/g, (match, key) => { + return values[key] !== undefined ? String(values[key]) : match; + }); +}; +const getHostEventsConfig = (viewConfig) => { + return { + shouldBypassPayloadValidation: viewConfig.shouldBypassPayloadValidation, + useHostEventsV2: viewConfig.useHostEventsV2, + }; +}; +/** + * Check if the window is undefined + * If the window is undefined, it means the code is running in a SSR environment. + * @returns true if the window is undefined, false otherwise + * + */ +const isWindowUndefined = () => { + if (typeof window === 'undefined') { + logger$3.error(ERROR_MESSAGE.SSR_ENVIRONMENT_ERROR); + return true; + } + return false; +}; +/** + * Validates that a URL uses only http: or https: protocols. + * Returns a tuple of [isValid, error] so the caller can handle validation errors. + * @param url - The URL string to validate + * @returns [true, null] if valid, [false, Error] if invalid + */ +const validateHttpUrl = (url) => { + try { + const parsedUrl = new URL(url); + if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') { + return [false, new Error(`Invalid protocol: ${parsedUrl.protocol}. Only http: and https: are allowed.`)]; + } + return [true, null]; + } + catch (error) { + return [false, error instanceof Error ? error : new Error(String(error))]; + } +}; +/** + * Sets a query parameter if the value is defined. + * @param queryParams - The query params object to modify + * @param param - The parameter key + * @param value - The value to set + * @param asBoolean - If true, coerces value to boolean + */ +const setParamIfDefined = (queryParams, param, value, asBoolean = false) => { + if (value !== undefined) { + queryParams[param] = asBoolean ? !!value : value; + } +}; + +/** Used for built-in method references. */ +var objectProto$c = Object.prototype; + +/** + * Checks if `value` is likely a prototype object. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a prototype, else `false`. + */ +function isPrototype(value) { + var Ctor = value && value.constructor, + proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto$c; + + return value === proto; +} + +var _isPrototype = isPrototype; + +/** + * Creates a unary function that invokes `func` with its argument transformed. + * + * @private + * @param {Function} func The function to wrap. + * @param {Function} transform The argument transform. + * @returns {Function} Returns the new function. + */ +function overArg(func, transform) { + return function(arg) { + return func(transform(arg)); + }; +} + +var _overArg = overArg; + +/* Built-in method references for those with the same name as other `lodash` methods. */ +var nativeKeys = _overArg(Object.keys, Object); + +var _nativeKeys = nativeKeys; + +/** Used for built-in method references. */ +var objectProto$b = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty$a = objectProto$b.hasOwnProperty; + +/** + * The base implementation of `_.keys` which doesn't treat sparse arrays as dense. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + */ +function baseKeys(object) { + if (!_isPrototype(object)) { + return _nativeKeys(object); + } + var result = []; + for (var key in Object(object)) { + if (hasOwnProperty$a.call(object, key) && key != 'constructor') { + result.push(key); + } + } + return result; +} + +var _baseKeys = baseKeys; + +var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; + +function createCommonjsModule(fn) { + var module = { exports: {} }; + return fn(module, module.exports), module.exports; +} + +/** Detect free variable `global` from Node.js. */ + +var freeGlobal = typeof commonjsGlobal == 'object' && commonjsGlobal && commonjsGlobal.Object === Object && commonjsGlobal; + +var _freeGlobal = freeGlobal; + +/** Detect free variable `self`. */ +var freeSelf = typeof self == 'object' && self && self.Object === Object && self; + +/** Used as a reference to the global object. */ +var root = _freeGlobal || freeSelf || Function('return this')(); + +var _root = root; + +/** Built-in value references. */ +var Symbol$1 = _root.Symbol; + +var _Symbol = Symbol$1; + +/** Used for built-in method references. */ +var objectProto$a = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty$9 = objectProto$a.hasOwnProperty; + +/** + * Used to resolve the + * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) + * of values. + */ +var nativeObjectToString$1 = objectProto$a.toString; + +/** Built-in value references. */ +var symToStringTag$1 = _Symbol ? _Symbol.toStringTag : undefined; + +/** + * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the raw `toStringTag`. + */ +function getRawTag(value) { + var isOwn = hasOwnProperty$9.call(value, symToStringTag$1), + tag = value[symToStringTag$1]; + + try { + value[symToStringTag$1] = undefined; + var unmasked = true; + } catch (e) {} + + var result = nativeObjectToString$1.call(value); + if (unmasked) { + if (isOwn) { + value[symToStringTag$1] = tag; + } else { + delete value[symToStringTag$1]; + } + } + return result; +} + +var _getRawTag = getRawTag; + +/** Used for built-in method references. */ +var objectProto$9 = Object.prototype; + +/** + * Used to resolve the + * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) + * of values. + */ +var nativeObjectToString = objectProto$9.toString; + +/** + * Converts `value` to a string using `Object.prototype.toString`. + * + * @private + * @param {*} value The value to convert. + * @returns {string} Returns the converted string. + */ +function objectToString(value) { + return nativeObjectToString.call(value); +} + +var _objectToString = objectToString; + +/** `Object#toString` result references. */ +var nullTag$1 = '[object Null]', + undefinedTag = '[object Undefined]'; + +/** Built-in value references. */ +var symToStringTag = _Symbol ? _Symbol.toStringTag : undefined; + +/** + * The base implementation of `getTag` without fallbacks for buggy environments. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the `toStringTag`. + */ +function baseGetTag(value) { + if (value == null) { + return value === undefined ? undefinedTag : nullTag$1; + } + return (symToStringTag && symToStringTag in Object(value)) + ? _getRawTag(value) + : _objectToString(value); +} + +var _baseGetTag = baseGetTag; + +/** + * Checks if `value` is the + * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) + * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(_.noop); + * // => true + * + * _.isObject(null); + * // => false + */ +function isObject(value) { + var type = typeof value; + return value != null && (type == 'object' || type == 'function'); +} + +var isObject_1 = isObject; + +/** `Object#toString` result references. */ +var asyncTag = '[object AsyncFunction]', + funcTag$1 = '[object Function]', + genTag = '[object GeneratorFunction]', + proxyTag = '[object Proxy]'; + +/** + * Checks if `value` is classified as a `Function` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a function, else `false`. + * @example + * + * _.isFunction(_); + * // => true + * + * _.isFunction(/abc/); + * // => false + */ +function isFunction(value) { + if (!isObject_1(value)) { + return false; + } + // The use of `Object#toString` avoids issues with the `typeof` operator + // in Safari 9 which returns 'object' for typed arrays and other constructors. + var tag = _baseGetTag(value); + return tag == funcTag$1 || tag == genTag || tag == asyncTag || tag == proxyTag; +} + +var isFunction_1 = isFunction; + +/** Used to detect overreaching core-js shims. */ +var coreJsData = _root['__core-js_shared__']; + +var _coreJsData = coreJsData; + +/** Used to detect methods masquerading as native. */ +var maskSrcKey = (function() { + var uid = /[^.]+$/.exec(_coreJsData && _coreJsData.keys && _coreJsData.keys.IE_PROTO || ''); + return uid ? ('Symbol(src)_1.' + uid) : ''; +}()); + +/** + * Checks if `func` has its source masked. + * + * @private + * @param {Function} func The function to check. + * @returns {boolean} Returns `true` if `func` is masked, else `false`. + */ +function isMasked(func) { + return !!maskSrcKey && (maskSrcKey in func); +} + +var _isMasked = isMasked; + +/** Used for built-in method references. */ +var funcProto$1 = Function.prototype; + +/** Used to resolve the decompiled source of functions. */ +var funcToString$1 = funcProto$1.toString; + +/** + * Converts `func` to its source code. + * + * @private + * @param {Function} func The function to convert. + * @returns {string} Returns the source code. + */ +function toSource(func) { + if (func != null) { + try { + return funcToString$1.call(func); + } catch (e) {} + try { + return (func + ''); + } catch (e) {} + } + return ''; +} + +var _toSource = toSource; + +/** + * Used to match `RegExp` + * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns). + */ +var reRegExpChar = /[\\^$.*+?()[\]{}|]/g; + +/** Used to detect host constructors (Safari). */ +var reIsHostCtor = /^\[object .+?Constructor\]$/; + +/** Used for built-in method references. */ +var funcProto = Function.prototype, + objectProto$8 = Object.prototype; + +/** Used to resolve the decompiled source of functions. */ +var funcToString = funcProto.toString; + +/** Used to check objects for own properties. */ +var hasOwnProperty$8 = objectProto$8.hasOwnProperty; + +/** Used to detect if a method is native. */ +var reIsNative = RegExp('^' + + funcToString.call(hasOwnProperty$8).replace(reRegExpChar, '\\$&') + .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' +); + +/** + * The base implementation of `_.isNative` without bad shim checks. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a native function, + * else `false`. + */ +function baseIsNative(value) { + if (!isObject_1(value) || _isMasked(value)) { + return false; + } + var pattern = isFunction_1(value) ? reIsNative : reIsHostCtor; + return pattern.test(_toSource(value)); +} + +var _baseIsNative = baseIsNative; + +/** + * Gets the value at `key` of `object`. + * + * @private + * @param {Object} [object] The object to query. + * @param {string} key The key of the property to get. + * @returns {*} Returns the property value. + */ +function getValue(object, key) { + return object == null ? undefined : object[key]; +} + +var _getValue = getValue; + +/** + * Gets the native function at `key` of `object`. + * + * @private + * @param {Object} object The object to query. + * @param {string} key The key of the method to get. + * @returns {*} Returns the function if it's native, else `undefined`. + */ +function getNative(object, key) { + var value = _getValue(object, key); + return _baseIsNative(value) ? value : undefined; +} + +var _getNative = getNative; + +/* Built-in method references that are verified to be native. */ +var DataView$1 = _getNative(_root, 'DataView'); + +var _DataView = DataView$1; + +/* Built-in method references that are verified to be native. */ +var Map$1 = _getNative(_root, 'Map'); + +var _Map = Map$1; + +/* Built-in method references that are verified to be native. */ +var Promise$1 = _getNative(_root, 'Promise'); + +var _Promise = Promise$1; + +/* Built-in method references that are verified to be native. */ +var Set$1 = _getNative(_root, 'Set'); + +var _Set = Set$1; + +/* Built-in method references that are verified to be native. */ +var WeakMap = _getNative(_root, 'WeakMap'); + +var _WeakMap = WeakMap; + +/** `Object#toString` result references. */ +var mapTag$3 = '[object Map]', + objectTag$2 = '[object Object]', + promiseTag = '[object Promise]', + setTag$3 = '[object Set]', + weakMapTag$1 = '[object WeakMap]'; + +var dataViewTag$2 = '[object DataView]'; + +/** Used to detect maps, sets, and weakmaps. */ +var dataViewCtorString = _toSource(_DataView), + mapCtorString = _toSource(_Map), + promiseCtorString = _toSource(_Promise), + setCtorString = _toSource(_Set), + weakMapCtorString = _toSource(_WeakMap); + +/** + * Gets the `toStringTag` of `value`. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the `toStringTag`. + */ +var getTag = _baseGetTag; + +// Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6. +if ((_DataView && getTag(new _DataView(new ArrayBuffer(1))) != dataViewTag$2) || + (_Map && getTag(new _Map) != mapTag$3) || + (_Promise && getTag(_Promise.resolve()) != promiseTag) || + (_Set && getTag(new _Set) != setTag$3) || + (_WeakMap && getTag(new _WeakMap) != weakMapTag$1)) { + getTag = function(value) { + var result = _baseGetTag(value), + Ctor = result == objectTag$2 ? value.constructor : undefined, + ctorString = Ctor ? _toSource(Ctor) : ''; + + if (ctorString) { + switch (ctorString) { + case dataViewCtorString: return dataViewTag$2; + case mapCtorString: return mapTag$3; + case promiseCtorString: return promiseTag; + case setCtorString: return setTag$3; + case weakMapCtorString: return weakMapTag$1; + } + } + return result; + }; +} + +var _getTag = getTag; + +/** + * Checks if `value` is object-like. A value is object-like if it's not `null` + * and has a `typeof` result of "object". + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + * @example + * + * _.isObjectLike({}); + * // => true + * + * _.isObjectLike([1, 2, 3]); + * // => true + * + * _.isObjectLike(_.noop); + * // => false + * + * _.isObjectLike(null); + * // => false + */ +function isObjectLike(value) { + return value != null && typeof value == 'object'; +} + +var isObjectLike_1 = isObjectLike; + +/** `Object#toString` result references. */ +var argsTag$2 = '[object Arguments]'; + +/** + * The base implementation of `_.isArguments`. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an `arguments` object, + */ +function baseIsArguments(value) { + return isObjectLike_1(value) && _baseGetTag(value) == argsTag$2; +} + +var _baseIsArguments = baseIsArguments; + +/** Used for built-in method references. */ +var objectProto$7 = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty$7 = objectProto$7.hasOwnProperty; + +/** Built-in value references. */ +var propertyIsEnumerable$1 = objectProto$7.propertyIsEnumerable; + +/** + * Checks if `value` is likely an `arguments` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an `arguments` object, + * else `false`. + * @example + * + * _.isArguments(function() { return arguments; }()); + * // => true + * + * _.isArguments([1, 2, 3]); + * // => false + */ +var isArguments = _baseIsArguments(function() { return arguments; }()) ? _baseIsArguments : function(value) { + return isObjectLike_1(value) && hasOwnProperty$7.call(value, 'callee') && + !propertyIsEnumerable$1.call(value, 'callee'); +}; + +var isArguments_1 = isArguments; + +/** + * Checks if `value` is classified as an `Array` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array, else `false`. + * @example + * + * _.isArray([1, 2, 3]); + * // => true + * + * _.isArray(document.body.children); + * // => false + * + * _.isArray('abc'); + * // => false + * + * _.isArray(_.noop); + * // => false + */ +var isArray = Array.isArray; + +var isArray_1 = isArray; + +/** Used as references for various `Number` constants. */ +var MAX_SAFE_INTEGER$1 = 9007199254740991; + +/** + * Checks if `value` is a valid array-like length. + * + * **Note:** This method is loosely based on + * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. + * @example + * + * _.isLength(3); + * // => true + * + * _.isLength(Number.MIN_VALUE); + * // => false + * + * _.isLength(Infinity); + * // => false + * + * _.isLength('3'); + * // => false + */ +function isLength(value) { + return typeof value == 'number' && + value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER$1; +} + +var isLength_1 = isLength; + +/** + * Checks if `value` is array-like. A value is considered array-like if it's + * not a function and has a `value.length` that's an integer greater than or + * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is array-like, else `false`. + * @example + * + * _.isArrayLike([1, 2, 3]); + * // => true + * + * _.isArrayLike(document.body.children); + * // => true + * + * _.isArrayLike('abc'); + * // => true + * + * _.isArrayLike(_.noop); + * // => false + */ +function isArrayLike(value) { + return value != null && isLength_1(value.length) && !isFunction_1(value); +} + +var isArrayLike_1 = isArrayLike; + +/** + * This method returns `false`. + * + * @static + * @memberOf _ + * @since 4.13.0 + * @category Util + * @returns {boolean} Returns `false`. + * @example + * + * _.times(2, _.stubFalse); + * // => [false, false] + */ +function stubFalse() { + return false; +} + +var stubFalse_1 = stubFalse; + +var isBuffer_1 = createCommonjsModule(function (module, exports$1) { +/** Detect free variable `exports`. */ +var freeExports = exports$1 && !exports$1.nodeType && exports$1; + +/** Detect free variable `module`. */ +var freeModule = freeExports && 'object' == 'object' && module && !module.nodeType && module; + +/** Detect the popular CommonJS extension `module.exports`. */ +var moduleExports = freeModule && freeModule.exports === freeExports; + +/** Built-in value references. */ +var Buffer = moduleExports ? _root.Buffer : undefined; + +/* Built-in method references for those with the same name as other `lodash` methods. */ +var nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined; + +/** + * Checks if `value` is a buffer. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a buffer, else `false`. + * @example + * + * _.isBuffer(new Buffer(2)); + * // => true + * + * _.isBuffer(new Uint8Array(2)); + * // => false + */ +var isBuffer = nativeIsBuffer || stubFalse_1; + +module.exports = isBuffer; +}); + +/** `Object#toString` result references. */ +var argsTag$1 = '[object Arguments]', + arrayTag$1 = '[object Array]', + boolTag$2 = '[object Boolean]', + dateTag$1 = '[object Date]', + errorTag$1 = '[object Error]', + funcTag = '[object Function]', + mapTag$2 = '[object Map]', + numberTag$1 = '[object Number]', + objectTag$1 = '[object Object]', + regexpTag$1 = '[object RegExp]', + setTag$2 = '[object Set]', + stringTag$1 = '[object String]', + weakMapTag = '[object WeakMap]'; + +var arrayBufferTag$1 = '[object ArrayBuffer]', + dataViewTag$1 = '[object DataView]', + float32Tag = '[object Float32Array]', + float64Tag = '[object Float64Array]', + int8Tag = '[object Int8Array]', + int16Tag = '[object Int16Array]', + int32Tag = '[object Int32Array]', + uint8Tag = '[object Uint8Array]', + uint8ClampedTag = '[object Uint8ClampedArray]', + uint16Tag = '[object Uint16Array]', + uint32Tag = '[object Uint32Array]'; + +/** Used to identify `toStringTag` values of typed arrays. */ +var typedArrayTags = {}; +typedArrayTags[float32Tag] = typedArrayTags[float64Tag] = +typedArrayTags[int8Tag] = typedArrayTags[int16Tag] = +typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] = +typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] = +typedArrayTags[uint32Tag] = true; +typedArrayTags[argsTag$1] = typedArrayTags[arrayTag$1] = +typedArrayTags[arrayBufferTag$1] = typedArrayTags[boolTag$2] = +typedArrayTags[dataViewTag$1] = typedArrayTags[dateTag$1] = +typedArrayTags[errorTag$1] = typedArrayTags[funcTag] = +typedArrayTags[mapTag$2] = typedArrayTags[numberTag$1] = +typedArrayTags[objectTag$1] = typedArrayTags[regexpTag$1] = +typedArrayTags[setTag$2] = typedArrayTags[stringTag$1] = +typedArrayTags[weakMapTag] = false; + +/** + * The base implementation of `_.isTypedArray` without Node.js optimizations. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. + */ +function baseIsTypedArray(value) { + return isObjectLike_1(value) && + isLength_1(value.length) && !!typedArrayTags[_baseGetTag(value)]; +} + +var _baseIsTypedArray = baseIsTypedArray; + +/** + * The base implementation of `_.unary` without support for storing metadata. + * + * @private + * @param {Function} func The function to cap arguments for. + * @returns {Function} Returns the new capped function. + */ +function baseUnary(func) { + return function(value) { + return func(value); + }; +} + +var _baseUnary = baseUnary; + +var _nodeUtil = createCommonjsModule(function (module, exports$1) { +/** Detect free variable `exports`. */ +var freeExports = exports$1 && !exports$1.nodeType && exports$1; + +/** Detect free variable `module`. */ +var freeModule = freeExports && 'object' == 'object' && module && !module.nodeType && module; + +/** Detect the popular CommonJS extension `module.exports`. */ +var moduleExports = freeModule && freeModule.exports === freeExports; + +/** Detect free variable `process` from Node.js. */ +var freeProcess = moduleExports && _freeGlobal.process; + +/** Used to access faster Node.js helpers. */ +var nodeUtil = (function() { + try { + // Use `util.types` for Node.js 10+. + var types = freeModule && freeModule.require && freeModule.require('util').types; + + if (types) { + return types; + } + + // Legacy `process.binding('util')` for Node.js < 10. + return freeProcess && freeProcess.binding && freeProcess.binding('util'); + } catch (e) {} +}()); + +module.exports = nodeUtil; +}); + +/* Node.js helper references. */ +var nodeIsTypedArray = _nodeUtil && _nodeUtil.isTypedArray; + +/** + * Checks if `value` is classified as a typed array. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. + * @example + * + * _.isTypedArray(new Uint8Array); + * // => true + * + * _.isTypedArray([]); + * // => false + */ +var isTypedArray = nodeIsTypedArray ? _baseUnary(nodeIsTypedArray) : _baseIsTypedArray; + +var isTypedArray_1 = isTypedArray; + +/** `Object#toString` result references. */ +var mapTag$1 = '[object Map]', + setTag$1 = '[object Set]'; + +/** Used for built-in method references. */ +var objectProto$6 = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty$6 = objectProto$6.hasOwnProperty; + +/** + * Checks if `value` is an empty object, collection, map, or set. + * + * Objects are considered empty if they have no own enumerable string keyed + * properties. + * + * Array-like values such as `arguments` objects, arrays, buffers, strings, or + * jQuery-like collections are considered empty if they have a `length` of `0`. + * Similarly, maps and sets are considered empty if they have a `size` of `0`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is empty, else `false`. + * @example + * + * _.isEmpty(null); + * // => true + * + * _.isEmpty(true); + * // => true + * + * _.isEmpty(1); + * // => true + * + * _.isEmpty([1, 2, 3]); + * // => false + * + * _.isEmpty({ 'a': 1 }); + * // => false + */ +function isEmpty$1(value) { + if (value == null) { + return true; + } + if (isArrayLike_1(value) && + (isArray_1(value) || typeof value == 'string' || typeof value.splice == 'function' || + isBuffer_1(value) || isTypedArray_1(value) || isArguments_1(value))) { + return !value.length; + } + var tag = _getTag(value); + if (tag == mapTag$1 || tag == setTag$1) { + return !value.size; + } + if (_isPrototype(value)) { + return !_baseKeys(value).length; + } + for (var key in value) { + if (hasOwnProperty$6.call(value, key)) { + return false; + } + } + return true; +} + +var isEmpty_1 = isEmpty$1; + +var UIPassthroughEvent; +(function (UIPassthroughEvent) { + UIPassthroughEvent["PinAnswerToLiveboard"] = "addVizToPinboard"; + UIPassthroughEvent["SaveAnswer"] = "saveAnswer"; + UIPassthroughEvent["GetDiscoverabilityStatus"] = "getDiscoverabilityStatus"; + UIPassthroughEvent["GetAvailableUIPassthroughs"] = "getAvailableUiPassthroughs"; + UIPassthroughEvent["GetAnswerConfig"] = "getAnswerPageConfig"; + UIPassthroughEvent["GetLiveboardConfig"] = "getPinboardPageConfig"; + UIPassthroughEvent["GetUnsavedAnswerTML"] = "getUnsavedAnswerTML"; + UIPassthroughEvent["UpdateFilters"] = "updateFilters"; + UIPassthroughEvent["Drilldown"] = "drillDown"; + UIPassthroughEvent["GetAnswerSession"] = "getAnswerSession"; + UIPassthroughEvent["GetFilters"] = "getFilters"; + UIPassthroughEvent["GetIframeUrl"] = "getIframeUrl"; + UIPassthroughEvent["GetParameters"] = "getParameters"; + UIPassthroughEvent["GetTML"] = "getTML"; + UIPassthroughEvent["GetTabs"] = "getTabs"; + UIPassthroughEvent["GetExportRequestForCurrentPinboard"] = "getExportRequestForCurrentPinboard"; +})(UIPassthroughEvent || (UIPassthroughEvent = {})); + +const EndPoints = { + AUTH_VERIFICATION: '/callosum/v1/session/info', + SESSION_INFO: '/callosum/v1/session/info', + PREAUTH_INFO: '/prism/preauth/info', + SAML_LOGIN_TEMPLATE: (targetUrl) => `/callosum/v1/saml/login?targetURLPath=${targetUrl}`, + OIDC_LOGIN_TEMPLATE: (targetUrl) => `/callosum/v1/oidc/login?targetURLPath=${targetUrl}`, + TOKEN_LOGIN: '/callosum/v1/session/login/token', + BASIC_LOGIN: '/callosum/v1/session/login', + LOGOUT: '/callosum/v1/session/logout', + EXECUTE_TML: '/api/rest/2.0/metadata/tml/import', + EXPORT_TML: '/api/rest/2.0/metadata/tml/export', + IS_ACTIVE: '/callosum/v1/session/isactive', +}; +/** + * + * @param url + * @param options + */ +function failureLoggedFetch(url, options = {}) { + return fetch(url, options).then(async (r) => { + if (!r.ok && r.type !== 'opaqueredirect' && r.type !== 'opaque') { + logger$3.error('Failure', await r.text?.()); + } + return r; + }); +} +/** + * Service to validate a auth token against a ThoughtSpot host. + * @param thoughtSpotHost : ThoughtSpot host to verify the token against. + * @param authToken : Auth token to verify. + */ +async function verifyTokenService(thoughtSpotHost, authToken) { + const authVerificationUrl = `${thoughtSpotHost}${EndPoints.IS_ACTIVE}`; + try { + const res = await fetch(authVerificationUrl, { + headers: { + Authorization: `Bearer ${authToken}`, + 'x-requested-by': 'ThoughtSpot', + }, + credentials: 'omit', + }); + return res.ok; + } + catch (e) { + logger$3.warn(`Token Verification Service failed : ${e.message}`); + } + return false; +} +/** + * + * @param authEndpoint + */ +async function fetchAuthTokenService(authEndpoint) { + return fetch(authEndpoint); +} +/** + * + * @param thoughtSpotHost + * @param username + * @param authToken + */ +async function fetchAuthService(thoughtSpotHost, username, authToken) { + const fetchUrlParams = username + ? `username=${username}&auth_token=${authToken}` + : `auth_token=${authToken}`; + return failureLoggedFetch(`${thoughtSpotHost}${EndPoints.TOKEN_LOGIN}?${fetchUrlParams}`, { + credentials: 'include', + // We do not want to follow the redirect, as it starts giving a CORS + // error + redirect: 'manual', + }); +} +/** + * + * @param thoughtSpotHost + * @param username + * @param authToken + */ +async function fetchAuthPostService(thoughtSpotHost, username, authToken) { + const bodyPrams = username + ? `username=${encodeURIComponent(username)}&auth_token=${encodeURIComponent(authToken)}` + : `auth_token=${encodeURIComponent(authToken)}`; + return failureLoggedFetch(`${thoughtSpotHost}${EndPoints.TOKEN_LOGIN}`, { + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'x-requested-by': 'ThoughtSpot', + }, + body: bodyPrams, + credentials: 'include', + // We do not want to follow the redirect, as it starts giving a CORS + // error + redirect: 'manual', + }); +} +/** + * + * @param thoughtSpotHost + * @param username + * @param password + */ +async function fetchBasicAuthService(thoughtSpotHost, username, password) { + return failureLoggedFetch(`${thoughtSpotHost}${EndPoints.BASIC_LOGIN}`, { + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'x-requested-by': 'ThoughtSpot', + }, + body: `username=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}`, + credentials: 'include', + }); +} + +const cacheAuthTokenKey = 'cachedAuthToken'; +const getCacheAuthToken = () => getValueFromWindow(cacheAuthTokenKey); +const storeAuthTokenInCache = (token) => { + storeValueInWindow(cacheAuthTokenKey, token); +}; +// This method can be used to get the authToken using the embedConfig +/** + * + * @param embedConfig + */ +async function getAuthenticationToken(embedConfig, skipvalidation = false) { + const cachedAuthToken = getCacheAuthToken(); + // Since we don't have token validation enabled , we cannot tell if the + // cached token is valid or not. So we will always fetch a new token. + if (cachedAuthToken && !embedConfig.disableTokenVerification && !skipvalidation) { + let isCachedTokenStillValid; + try { + isCachedTokenStillValid = await validateAuthToken(embedConfig, cachedAuthToken, true); + } + catch { + isCachedTokenStillValid = false; + } + if (isCachedTokenStillValid) + return cachedAuthToken; + } + const { authEndpoint, getAuthToken } = embedConfig; + let authToken = null; + if (getAuthToken) { + authToken = await getAuthToken(); + } + else { + const response = await fetchAuthTokenService(authEndpoint); + authToken = await response.text(); + } + try { + // this will throw error if the token is not valid + await validateAuthToken(embedConfig, authToken); + } + catch (e) { + logger$3.error(`${ERROR_MESSAGE.INVALID_TOKEN_ERROR} Error : ${e.message}`); + throw e; + } + storeAuthTokenInCache(authToken); + return authToken; +} +const validateAuthToken = async (embedConfig, authToken, suppressAlert) => { + // even if token verification is disabled, we will still validate + // that the token is a string before proceeding. + if (typeof authToken !== 'string') { + const errorMessage = formatTemplate(ERROR_MESSAGE.INVALID_TOKEN_TYPE_ERROR, { + invalidType: typeof authToken, + }); + logger$3.error(errorMessage); + throw new Error(errorMessage); + } + const cachedAuthToken = getCacheAuthToken(); + if (embedConfig.disableTokenVerification) { + logger$3.info('Token verification is disabled. Assuming token is valid.'); + return true; + } + try { + const isTokenValid = await verifyTokenService(embedConfig.thoughtSpotHost, authToken); + if (isTokenValid) + return true; + } + catch { + return false; + } + if (cachedAuthToken && cachedAuthToken === authToken) { + if (!embedConfig.suppressErrorAlerts && !suppressAlert) { + alert(ERROR_MESSAGE.DUPLICATE_TOKEN_ERR); + } + throw new Error(ERROR_MESSAGE.DUPLICATE_TOKEN_ERR); + } + else { + throw new Error(ERROR_MESSAGE.INVALID_TOKEN_ERROR); + } +}; +/** + * Resets the auth token and a new token will be fetched on the next request. + * @example + * ```js + * resetCachedAuthToken(); + * ``` + * @version SDK: 1.28.0 | ThoughtSpot: * + * @group Authentication / Init + */ +const resetCachedAuthToken = () => { + storeAuthTokenInCache(null); +}; + +const configKey = 'embedConfig'; +/** + * Gets the embed configuration settings that were used to + * initialize the SDK. + * @returns {@link EmbedConfig} + * + * @example + * ```js + * import { getInitConfig } from '@thoughtspot/visual-embed-sdk'; + * // Call the getInitConfig method to retrieve the embed configuration + * const config = getInitConfig(); + * // Log the configuration settings + * console.log(config); + * ``` + * Returns the link:https://developers.thoughtspot.com/docs/Interface_EmbedConfig[EmbedConfig] + * object, which contains the configuration settings used to + * initialize the SDK, including the following: + * + * - `thoughtSpotHost` - ThoughtSpot host URL + * - `authType`: The authentication method used. For example, + * `AuthServerCookieless` for `AuthType.TrustedAuthTokenCookieless` + * - `customizations` - Style, text, and icon customization settings + * that were applied during the SDK initialization. + * + * The following JSON output shows the embed configuration + * settings returned from the code in the previous example: + * + * @example + * ```json + * { + * "thoughtSpotHost": "https://{ThoughtSpot-Host}", + * "authType": "AuthServerCookieless", + * "customizations": { + * "style": { + * "customCSS": { + * "variables": { + * "--ts-var-button--secondary-background": "#7492d5", + * "--ts-var-button--secondary--hovers-background": "#aac2f8", + * "--ts-var-root-background": "#f1f4f8" + * } + * } + * } + * }, + * "loginFailedMessage": "Login failed, please try again", + * "authTriggerText": "Authorize", + * "disableTokenVerification": true, + * "authTriggerContainer": "#your-own-div" + * } + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw, and later + * @group Global methods + */ +const getEmbedConfig = () => getValueFromWindow(configKey) || {}; +/** + * Sets the configuration embed was initialized with. + * And returns the new configuration. + * @param newConfig The configuration to set. + * @version SDK: 1.27.0 | ThoughtSpot: 9.8.0.cl, 9.8.1.sw, and later + * @group Global methods + */ +const setEmbedConfig = (newConfig) => { + storeValueInWindow(configKey, newConfig); + return getValueFromWindow(configKey); +}; + +/** + * Fetch wrapper that adds the authentication token to the request. + * Use this to call the ThoughtSpot APIs when using the visual embed sdk. + * The interface for this method is the same as Web `Fetch`. + * @param input + * @param init + * @example + * ```js + * tokenizedFetch("/api/rest/2.0/auth/session/user", { + * // .. fetch options .. + * }); + * ``` + * @version SDK: 1.28.0 + * @group Global methods + */ +const tokenizedFetch = async (input, init) => { + const embedConfig = getEmbedConfig(); + const options = { ...init }; + let token; + if (embedConfig.authType !== AuthType.TrustedAuthTokenCookieless) { + token = getCacheAuthToken(); + if (!token) { + return fetch(input, { ...options, credentials: 'include' }); + } + } + else { + token = await getAuthenticationToken(embedConfig); + } + const req = new Request(input, options); + if (token) { + req.headers.append('Authorization', `Bearer ${token}`); + } + return fetch(req); +}; + +/** + * + * @param root0 + * @param root0.query + * @param root0.variables + * @param root0.thoughtSpotHost + * @param root0.isCompositeQuery + */ +async function graphqlQuery({ query, variables, thoughtSpotHost, isCompositeQuery = false, }) { + const operationName = getOperationNameFromQuery(query); + try { + const response = await tokenizedFetch(`${thoughtSpotHost}/prism/?op=${operationName}`, { + method: 'POST', + headers: { + 'content-type': 'application/json;charset=UTF-8', + 'x-requested-by': 'ThoughtSpot', + accept: '*/*', + 'accept-language': 'en-us', + }, + body: JSON.stringify({ + operationName, + query, + variables, + }), + credentials: 'include', + }); + const result = await response.json(); + const dataValues = Object.values(result.data); + return (isCompositeQuery) ? result.data : dataValues[0]; + } + catch (error) { + return error; + } +} + +const getSourceDetailQuery = ` + query GetSourceDetail($ids: [GUID!]!) { + getSourceDetailById(ids: $ids, type: LOGICAL_TABLE) { + id + name + description + authorName + authorDisplayName + isExternal + type + created + modified + columns { + id + name + author + authorDisplayName + description + dataType + type + modified + ownerName + owner + dataRecency + sources { + tableId + tableName + columnId + columnName + __typename + } + synonyms + cohortAnswerId + __typename + } + relationships + destinationRelationships + dataSourceId + __typename + } + } +`; +const sourceDetailCache = new Map(); +/** + * + * @param thoughtSpotHost + * @param sourceId + */ +async function getSourceDetail(thoughtSpotHost, sourceId) { + if (sourceDetailCache.get(sourceId)) { + return sourceDetailCache.get(sourceId); + } + const details = await graphqlQuery({ + query: getSourceDetailQuery, + variables: { + ids: [sourceId], + }, + thoughtSpotHost, + }); + const souceDetails = details[0]; + if (souceDetails) { + sourceDetailCache.set(sourceId, souceDetails); + } + return souceDetails; +} + +const bachSessionId = ` +id { + sessionId + genNo + acSession { + sessionId + genNo + } +} +`; +const getUnaggregatedAnswerSession = ` +mutation GetUnAggregatedAnswerSession($session: BachSessionIdInput!, $columns: [UserPointSelectionInput!]!) { + Answer__getUnaggregatedAnswer(session: $session, columns: $columns) { + ${bachSessionId} + answer { + visualizations { + ... on TableViz { + columns { + column { + id + name + referencedColumns { + guid + displayName + } + } + } + } + } + } + } +} +`; +const removeColumns = ` +mutation RemoveColumns($session: BachSessionIdInput!, $logicalColumnIds: [GUID!], $columnIds: [GUID!]) { + Answer__removeColumns( + session: $session + logicalColumnIds: $logicalColumnIds + columnIds: $columnIds + ) { + ${bachSessionId} + } +} + `; +const addColumns = ` + mutation AddColumns($session: BachSessionIdInput!, $columns: [AnswerColumnInfo!]!) { + Answer__addColumn(session: $session, columns: $columns) { + ${bachSessionId} + } + } + `; +const addFilter = ` + mutation AddUpdateFilter($session: BachSessionIdInput!, $params: AddUpdateFilterInput!) { + Answer__addUpdateFilter(session: $session, params: $params) { + ${bachSessionId} + } + } +`; +const getAnswer = ` + query GetAnswer($session: BachSessionIdInput!) { + getAnswer(session: $session) { + ${bachSessionId} + answer { + id + name + description + displayMode + sources { + header { + guid + displayName + } + } + filterGroups { + columnInfo { + name + referencedColumns { + guid + displayName + } + } + filters { + filterContent { + filterType + negate + value { + key + } + } + } + } + metadata { + author + authorId + createdAt + isDiscoverable + isHidden + modifiedAt + } + visualizations { + ... on TableViz { + id + columns { + column { + id + name + referencedColumns { + guid + displayName + } + } + } + } + ... on ChartViz { + id + } + } + } + } + } + +`; +const getAnswerData = ` + query GetTableWithHeadlineData($session: BachSessionIdInput!, $deadline: Int!, $dataPaginationParams: DataPaginationParamsInput!) { + getAnswer(session: $session) { + ${bachSessionId} + answer { + id + visualizations { + id + ... on TableViz { + columns { + column { + id + name + type + aggregationType + dataType + } + } + data(deadline: $deadline, pagination: $dataPaginationParams) + } + } + } + } + } +`; +const addVizToLiveboard = ` + mutation AddVizToLiveboard(liveboardId: GUID!, session: BachSessionIdInput!, tabId: GUID, vizId: GUID!) { + Answer__addVizToPinboard( + pinboardId: liveboardId + + session: $session + + tabId: $tabId + + vizId: $vizId + ) { + ${bachSessionId} + } + } +`; +const getSQLQuery = ` + mutation GetSQLQuery($session: BachSessionIdInput!) { + Answer__getQuery(session: $session) { + sql + } + } +`; +const updateDisplayMode = ` + mutation UpdateDisplayMode( + $session: BachSessionIdInput! + $displayMode: DisplayMode +) { + Answer__updateProperties(session: $session, displayMode: $displayMode) { + id { + sessionId + genNo + acSession { + sessionId + genNo + } + } + answer { + id + displayMode + suggestedDisplayMode + } + } +} +`; +const getAnswerTML = ` +mutation GetUnsavedAnswerTML($session: BachSessionIdInput!, $exportDependencies: Boolean, $formatType: EDocFormatType, $exportPermissions: Boolean, $exportFqn: Boolean) { + UnsavedAnswer_getTML( + session: $session + exportDependencies: $exportDependencies + formatType: $formatType + exportPermissions: $exportPermissions + exportFqn: $exportFqn + ) { + zipFile + object { + edoc + name + type + __typename + } + __typename + } +}`; + +// import YAML from 'yaml'; +var OperationType; +(function (OperationType) { + OperationType["GetChartWithData"] = "GetChartWithData"; + OperationType["GetTableWithHeadlineData"] = "GetTableWithHeadlineData"; +})(OperationType || (OperationType = {})); +const DATA_TYPES = ['DATE', 'DATE_TIME', 'TIME']; +/** + * AnswerService provides a simple way to work with ThoughtSpot Answers. + * + * This service allows you to interact with ThoughtSpot Answers programmatically, + * making it easy to customize visualizations, filter data, and extract insights + * directly from your application. + * + * You can use this service to: + * + * - Add or remove columns from Answers (`addColumns`, `removeColumns`, + * `addColumnsByName`) + * - Apply filters to Answers (`addFilter`) + * - Get data from Answers in different formats (JSON, CSV, PNG) (`fetchData`, + * `fetchCSVBlob`, `fetchPNGBlob`) + * - Get data for specific points in visualizations + * (`getUnderlyingDataForPoint`) + * - Run custom queries (`executeQuery`) + * - Add visualizations to Liveboards (`addDisplayedVizToLiveboard`) + * + * @example + * ```js + * // Get the answer service + * embed.on(EmbedEvent.Data, async (e) => { + * const service = await embed.getAnswerService(); + * + * // Add columns to the answer + * await service.addColumnsByName(["Sales", "Region"]); + * + * // Get the data + * const data = await service.fetchData(); + * console.log(data); + * }); + * ``` + * + * @example + * ```js + * // Get data for a point in a visualization + * embed.on(EmbedEvent.CustomAction, async (e) => { + * const underlying = await e.answerService.getUnderlyingDataForPoint([ + * 'Product Name', + * 'Sales Amount' + * ]); + * + * const data = await underlying.fetchData(0, 100); + * console.log(data); + * }); + * ``` + * + * @version SDK: 1.25.0 | ThoughtSpot: 9.10.0.cl + * @group Events + */ +class AnswerService { + /** + * Should not need to be called directly. + * @param session + * @param answer + * @param thoughtSpotHost + * @param selectedPoints + */ + constructor(session, answer, thoughtSpotHost, selectedPoints) { + this.session = session; + this.thoughtSpotHost = thoughtSpotHost; + this.selectedPoints = selectedPoints; + this.tmlOverride = {}; + this.session = removeTypename(session); + this.answer = answer; + } + /** + * Get the details about the source used in the answer. + * This can be used to get the list of all columns in the data source for example. + */ + async getSourceDetail() { + const sourceId = (await this.getAnswer()).sources[0].header.guid; + return getSourceDetail(this.thoughtSpotHost, sourceId); + } + /** + * Remove columnIds and return updated answer session. + * @param columnIds + * @returns + */ + async removeColumns(columnIds) { + return this.executeQuery(removeColumns, { + logicalColumnIds: columnIds, + }); + } + /** + * Add columnIds and return updated answer session. + * @param columnIds + * @returns + */ + async addColumns(columnIds) { + return this.executeQuery(addColumns, { + columns: columnIds.map((colId) => ({ logicalColumnId: colId })), + }); + } + /** + * Add columns by names and return updated answer session. + * @param columnNames + * @returns + * @example + * ```js + * embed.on(EmbedEvent.Data, async (e) => { + * const service = await embed.getAnswerService(); + * await service.addColumnsByName([ + * "col name 1", + * "col name 2" + * ]); + * console.log(await service.fetchData()); + * }); + * ``` + */ + async addColumnsByName(columnNames) { + const sourceDetail = await this.getSourceDetail(); + const columnGuids = getGuidsFromColumnNames(sourceDetail, columnNames); + return this.addColumns([...columnGuids]); + } + /** + * Add a filter to the answer. + * @param columnName + * @param operator + * @param values + * @returns + */ + async addFilter(columnName, operator, values) { + const sourceDetail = await this.getSourceDetail(); + const columnGuids = getGuidsFromColumnNames(sourceDetail, [columnName]); + return this.executeQuery(addFilter, { + params: { + filterContent: [{ + filterType: operator, + value: values.map((v) => { + const [type, prefix] = getTypeFromValue(v); + return { + type: type.toUpperCase(), + [`${prefix}Value`]: v, + }; + }), + }], + filterGroupId: { + logicalColumnId: columnGuids.values().next().value, + }, + }, + }); + } + async updateDisplayMode(displayMode = "TABLE_MODE") { + return this.executeQuery(updateDisplayMode, { + displayMode, + }); + } + async getSQLQuery(fetchSQLWithAllColumns = false) { + if (fetchSQLWithAllColumns) { + await this.updateDisplayMode("TABLE_MODE"); + } + const { sql } = await this.executeQuery(getSQLQuery, {}); + return sql; + } + /** + * Fetch data from the answer. + * @param offset + * @param size + * @returns + */ + async fetchData(offset = 0, size = 1000) { + const { answer } = await this.executeQuery(getAnswerData, { + deadline: 0, + dataPaginationParams: { + isClientPaginated: true, + offset, + size, + }, + }); + const { columns, data } = answer.visualizations.find((viz) => !!viz.data) || {}; + return { + columns, + data, + }; + } + /** + * Fetch the data for the answer as a CSV blob. This might be + * quicker for larger data. + * @param userLocale + * @param includeInfo Include the CSV header in the output + * @returns Response + */ + async fetchCSVBlob(userLocale = 'en-us', includeInfo = false) { + const fetchUrl = this.getFetchCSVBlobUrl(userLocale, includeInfo); + return tokenizedFetch(fetchUrl, { + credentials: 'include', + }); + } + /** + * Fetch the data for the answer as a PNG blob. This might be + * quicker for larger data. + * @param userLocale + * @param includeInfo + * @param omitBackground Omit the background in the PNG + * @param deviceScaleFactor The scale factor for the PNG + * @return Response + */ + async fetchPNGBlob(userLocale = 'en-us', omitBackground = false, deviceScaleFactor = 2) { + const fetchUrl = this.getFetchPNGBlobUrl(userLocale, omitBackground, deviceScaleFactor); + return tokenizedFetch(fetchUrl, { + credentials: 'include', + }); + } + /** + * Just get the internal URL for this answer's data + * as a CSV blob. + * @param userLocale + * @param includeInfo + * @returns + */ + getFetchCSVBlobUrl(userLocale = 'en-us', includeInfo = false) { + return `${this.thoughtSpotHost}/prism/download/answer/csv?sessionId=${this.session.sessionId}&genNo=${this.session.genNo}&userLocale=${userLocale}&exportFileName=data&hideCsvHeader=${!includeInfo}`; + } + /** + * Just get the internal URL for this answer's data + * as a PNG blob. + * @param userLocale + * @param omitBackground + * @param deviceScaleFactor + */ + getFetchPNGBlobUrl(userLocale = 'en-us', omitBackground = false, deviceScaleFactor = 2) { + return `${this.thoughtSpotHost}/prism/download/answer/png?sessionId=${this.session.sessionId}&deviceScaleFactor=${deviceScaleFactor}&omitBackground=${omitBackground}&genNo=${this.session.genNo}&userLocale=${userLocale}&exportFileName=data`; + } + /** + * Get underlying data given a point and the output column names. + * In case of a context menu action, the selectedPoints are + * automatically passed. + * @param outputColumnNames + * @param selectedPoints + * @example + * ```js + * embed.on(EmbedEvent.CustomAction, e => { + * const underlying = await e.answerService.getUnderlyingDataForPoint([ + * 'col name 1' // The column should exist in the data source. + * ]); + * const data = await underlying.fetchData(0, 100); + * }) + * ``` + * @version SDK: 1.25.0 | ThoughtSpot: 9.10.0.cl + */ + async getUnderlyingDataForPoint(outputColumnNames, selectedPoints) { + if (!selectedPoints && !this.selectedPoints) { + throw new Error('Needs to be triggered in context of a point'); + } + if (!selectedPoints) { + selectedPoints = getSelectedPointsForUnderlyingDataQuery(this.selectedPoints); + } + const sourceDetail = await this.getSourceDetail(); + const ouputColumnGuids = getGuidsFromColumnNames(sourceDetail, outputColumnNames); + const unAggAnswer = await graphqlQuery({ + query: getUnaggregatedAnswerSession, + variables: { + session: this.session, + columns: selectedPoints, + }, + thoughtSpotHost: this.thoughtSpotHost, + }); + const unaggAnswerSession = new AnswerService(unAggAnswer.id, unAggAnswer.answer, this.thoughtSpotHost); + const currentColumns = new Set(unAggAnswer.answer.visualizations[0].columns + .map((c) => c.column.referencedColumns[0].guid)); + const columnsToAdd = [...ouputColumnGuids].filter((col) => !currentColumns.has(col)); + if (columnsToAdd.length) { + await unaggAnswerSession.addColumns(columnsToAdd); + } + const columnsToRemove = [...currentColumns].filter((col) => !ouputColumnGuids.has(col)); + if (columnsToRemove.length) { + await unaggAnswerSession.removeColumns(columnsToRemove); + } + return unaggAnswerSession; + } + /** + * Execute a custom graphql query in the context of the answer. + * @param query graphql query + * @param variables graphql variables + * @returns + */ + async executeQuery(query, variables) { + const data = await graphqlQuery({ + query, + variables: { + session: this.session, + ...variables, + }, + thoughtSpotHost: this.thoughtSpotHost, + isCompositeQuery: false, + }); + this.session = deepMerge(this.session, data?.id || {}); + return data; + } + /** + * Get the internal session details for the answer. + * @returns + */ + getSession() { + return this.session; + } + async getAnswer() { + if (this.answer) { + return this.answer; + } + this.answer = this.executeQuery(getAnswer, {}).then((data) => data?.answer); + return this.answer; + } + async getTML() { + const { object } = await this.executeQuery(getAnswerTML, {}); + const edoc = object[0].edoc; + const YAML = await import('./index-Ck-r09gt.js'); + const parsedDoc = YAML.parse(edoc); + return { + answer: { + ...parsedDoc.answer, + ...this.tmlOverride, + }, + }; + } + async addDisplayedVizToLiveboard(liveboardId) { + const { displayMode, visualizations } = await this.getAnswer(); + const viz = getDisplayedViz(visualizations, displayMode); + return this.executeQuery(addVizToLiveboard, { + liveboardId, + vizId: viz.id, + }); + } + setTMLOverride(override) { + this.tmlOverride = override; + } +} +/** + * + * @param sourceDetail + * @param colNames + */ +function getGuidsFromColumnNames(sourceDetail, colNames) { + const cols = sourceDetail.columns.reduce((colSet, col) => { + colSet[col.name.toLowerCase()] = col; + return colSet; + }, {}); + return new Set(colNames.map((colName) => { + const col = cols[colName.toLowerCase()]; + return col.id; + })); +} +/** + * + * @param selectedPoints + */ +function getSelectedPointsForUnderlyingDataQuery(selectedPoints) { + const underlyingDataPoint = []; + /** + * + * @param colVal + */ + function addPointFromColVal(colVal) { + const dataType = colVal.column.dataType; + colVal.column.id; + let dataValue; + if (DATA_TYPES.includes(dataType)) { + if (Number.isFinite(colVal.value)) { + dataValue = [{ + epochRange: { + startEpoch: colVal.value, + }, + }]; + // Case for custom calendar. + } + else if (colVal.value?.v) { + dataValue = [{ + epochRange: { + startEpoch: colVal.value.v.s, + endEpoch: colVal.value.v.e, + }, + }]; + } + } + else { + dataValue = [{ value: colVal.value }]; + } + underlyingDataPoint.push({ + columnId: colVal.column.id, + dataValue, + }); + } + selectedPoints.forEach((p) => { + p.selectedAttributes.forEach(addPointFromColVal); + }); + return underlyingDataPoint; +} +/** + * + * @param visualizations + * @param displayMode + */ +function getDisplayedViz(visualizations, displayMode) { + if (displayMode === 'CHART_MODE') { + return visualizations.find((viz) => viz.__typename === 'ChartViz'); + } + return visualizations.find((viz) => viz.__typename === 'TableViz'); +} + +/** + * Appends the elements of `values` to `array`. + * + * @private + * @param {Array} array The array to modify. + * @param {Array} values The values to append. + * @returns {Array} Returns `array`. + */ +function arrayPush(array, values) { + var index = -1, + length = values.length, + offset = array.length; + + while (++index < length) { + array[offset + index] = values[index]; + } + return array; +} + +var _arrayPush = arrayPush; + +/** Built-in value references. */ +var spreadableSymbol = _Symbol ? _Symbol.isConcatSpreadable : undefined; + +/** + * Checks if `value` is a flattenable `arguments` object or array. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is flattenable, else `false`. + */ +function isFlattenable(value) { + return isArray_1(value) || isArguments_1(value) || + !!(spreadableSymbol && value && value[spreadableSymbol]); +} + +var _isFlattenable = isFlattenable; + +/** + * The base implementation of `_.flatten` with support for restricting flattening. + * + * @private + * @param {Array} array The array to flatten. + * @param {number} depth The maximum recursion depth. + * @param {boolean} [predicate=isFlattenable] The function invoked per iteration. + * @param {boolean} [isStrict] Restrict to values that pass `predicate` checks. + * @param {Array} [result=[]] The initial result value. + * @returns {Array} Returns the new flattened array. + */ +function baseFlatten(array, depth, predicate, isStrict, result) { + var index = -1, + length = array.length; + + predicate || (predicate = _isFlattenable); + result || (result = []); + + while (++index < length) { + var value = array[index]; + if (depth > 0 && predicate(value)) { + if (depth > 1) { + // Recursively flatten arrays (susceptible to call stack limits). + baseFlatten(value, depth - 1, predicate, isStrict, result); + } else { + _arrayPush(result, value); + } + } else if (!isStrict) { + result[result.length] = value; + } + } + return result; +} + +var _baseFlatten = baseFlatten; + +/** + * A specialized version of `_.map` for arrays without support for iteratee + * shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the new mapped array. + */ +function arrayMap(array, iteratee) { + var index = -1, + length = array == null ? 0 : array.length, + result = Array(length); + + while (++index < length) { + result[index] = iteratee(array[index], index, array); + } + return result; +} + +var _arrayMap = arrayMap; + +/** `Object#toString` result references. */ +var symbolTag$1 = '[object Symbol]'; + +/** + * Checks if `value` is classified as a `Symbol` primitive or object. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. + * @example + * + * _.isSymbol(Symbol.iterator); + * // => true + * + * _.isSymbol('abc'); + * // => false + */ +function isSymbol(value) { + return typeof value == 'symbol' || + (isObjectLike_1(value) && _baseGetTag(value) == symbolTag$1); +} + +var isSymbol_1 = isSymbol; + +/** Used to match property names within property paths. */ +var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/, + reIsPlainProp = /^\w*$/; + +/** + * Checks if `value` is a property name and not a property path. + * + * @private + * @param {*} value The value to check. + * @param {Object} [object] The object to query keys on. + * @returns {boolean} Returns `true` if `value` is a property name, else `false`. + */ +function isKey(value, object) { + if (isArray_1(value)) { + return false; + } + var type = typeof value; + if (type == 'number' || type == 'symbol' || type == 'boolean' || + value == null || isSymbol_1(value)) { + return true; + } + return reIsPlainProp.test(value) || !reIsDeepProp.test(value) || + (object != null && value in Object(object)); +} + +var _isKey = isKey; + +/* Built-in method references that are verified to be native. */ +var nativeCreate = _getNative(Object, 'create'); + +var _nativeCreate = nativeCreate; + +/** + * Removes all key-value entries from the hash. + * + * @private + * @name clear + * @memberOf Hash + */ +function hashClear() { + this.__data__ = _nativeCreate ? _nativeCreate(null) : {}; + this.size = 0; +} + +var _hashClear = hashClear; + +/** + * Removes `key` and its value from the hash. + * + * @private + * @name delete + * @memberOf Hash + * @param {Object} hash The hash to modify. + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ +function hashDelete(key) { + var result = this.has(key) && delete this.__data__[key]; + this.size -= result ? 1 : 0; + return result; +} + +var _hashDelete = hashDelete; + +/** Used to stand-in for `undefined` hash values. */ +var HASH_UNDEFINED$2 = '__lodash_hash_undefined__'; + +/** Used for built-in method references. */ +var objectProto$5 = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty$5 = objectProto$5.hasOwnProperty; + +/** + * Gets the hash value for `key`. + * + * @private + * @name get + * @memberOf Hash + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ +function hashGet(key) { + var data = this.__data__; + if (_nativeCreate) { + var result = data[key]; + return result === HASH_UNDEFINED$2 ? undefined : result; + } + return hasOwnProperty$5.call(data, key) ? data[key] : undefined; +} + +var _hashGet = hashGet; + +/** Used for built-in method references. */ +var objectProto$4 = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty$4 = objectProto$4.hasOwnProperty; + +/** + * Checks if a hash value for `key` exists. + * + * @private + * @name has + * @memberOf Hash + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ +function hashHas(key) { + var data = this.__data__; + return _nativeCreate ? (data[key] !== undefined) : hasOwnProperty$4.call(data, key); +} + +var _hashHas = hashHas; + +/** Used to stand-in for `undefined` hash values. */ +var HASH_UNDEFINED$1 = '__lodash_hash_undefined__'; + +/** + * Sets the hash `key` to `value`. + * + * @private + * @name set + * @memberOf Hash + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the hash instance. + */ +function hashSet(key, value) { + var data = this.__data__; + this.size += this.has(key) ? 0 : 1; + data[key] = (_nativeCreate && value === undefined) ? HASH_UNDEFINED$1 : value; + return this; +} + +var _hashSet = hashSet; + +/** + * Creates a hash object. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ +function Hash(entries) { + var index = -1, + length = entries == null ? 0 : entries.length; + + this.clear(); + while (++index < length) { + var entry = entries[index]; + this.set(entry[0], entry[1]); + } +} + +// Add methods to `Hash`. +Hash.prototype.clear = _hashClear; +Hash.prototype['delete'] = _hashDelete; +Hash.prototype.get = _hashGet; +Hash.prototype.has = _hashHas; +Hash.prototype.set = _hashSet; + +var _Hash = Hash; + +/** + * Removes all key-value entries from the list cache. + * + * @private + * @name clear + * @memberOf ListCache + */ +function listCacheClear() { + this.__data__ = []; + this.size = 0; +} + +var _listCacheClear = listCacheClear; + +/** + * Performs a + * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * comparison between two values to determine if they are equivalent. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + * @example + * + * var object = { 'a': 1 }; + * var other = { 'a': 1 }; + * + * _.eq(object, object); + * // => true + * + * _.eq(object, other); + * // => false + * + * _.eq('a', 'a'); + * // => true + * + * _.eq('a', Object('a')); + * // => false + * + * _.eq(NaN, NaN); + * // => true + */ +function eq(value, other) { + return value === other || (value !== value && other !== other); +} + +var eq_1 = eq; + +/** + * Gets the index at which the `key` is found in `array` of key-value pairs. + * + * @private + * @param {Array} array The array to inspect. + * @param {*} key The key to search for. + * @returns {number} Returns the index of the matched value, else `-1`. + */ +function assocIndexOf(array, key) { + var length = array.length; + while (length--) { + if (eq_1(array[length][0], key)) { + return length; + } + } + return -1; +} + +var _assocIndexOf = assocIndexOf; + +/** Used for built-in method references. */ +var arrayProto = Array.prototype; + +/** Built-in value references. */ +var splice = arrayProto.splice; + +/** + * Removes `key` and its value from the list cache. + * + * @private + * @name delete + * @memberOf ListCache + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ +function listCacheDelete(key) { + var data = this.__data__, + index = _assocIndexOf(data, key); + + if (index < 0) { + return false; + } + var lastIndex = data.length - 1; + if (index == lastIndex) { + data.pop(); + } else { + splice.call(data, index, 1); + } + --this.size; + return true; +} + +var _listCacheDelete = listCacheDelete; + +/** + * Gets the list cache value for `key`. + * + * @private + * @name get + * @memberOf ListCache + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ +function listCacheGet(key) { + var data = this.__data__, + index = _assocIndexOf(data, key); + + return index < 0 ? undefined : data[index][1]; +} + +var _listCacheGet = listCacheGet; + +/** + * Checks if a list cache value for `key` exists. + * + * @private + * @name has + * @memberOf ListCache + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ +function listCacheHas(key) { + return _assocIndexOf(this.__data__, key) > -1; +} + +var _listCacheHas = listCacheHas; + +/** + * Sets the list cache `key` to `value`. + * + * @private + * @name set + * @memberOf ListCache + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the list cache instance. + */ +function listCacheSet(key, value) { + var data = this.__data__, + index = _assocIndexOf(data, key); + + if (index < 0) { + ++this.size; + data.push([key, value]); + } else { + data[index][1] = value; + } + return this; +} + +var _listCacheSet = listCacheSet; + +/** + * Creates an list cache object. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ +function ListCache(entries) { + var index = -1, + length = entries == null ? 0 : entries.length; + + this.clear(); + while (++index < length) { + var entry = entries[index]; + this.set(entry[0], entry[1]); + } +} + +// Add methods to `ListCache`. +ListCache.prototype.clear = _listCacheClear; +ListCache.prototype['delete'] = _listCacheDelete; +ListCache.prototype.get = _listCacheGet; +ListCache.prototype.has = _listCacheHas; +ListCache.prototype.set = _listCacheSet; + +var _ListCache = ListCache; + +/** + * Removes all key-value entries from the map. + * + * @private + * @name clear + * @memberOf MapCache + */ +function mapCacheClear() { + this.size = 0; + this.__data__ = { + 'hash': new _Hash, + 'map': new (_Map || _ListCache), + 'string': new _Hash + }; +} + +var _mapCacheClear = mapCacheClear; + +/** + * Checks if `value` is suitable for use as unique object key. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is suitable, else `false`. + */ +function isKeyable(value) { + var type = typeof value; + return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean') + ? (value !== '__proto__') + : (value === null); +} + +var _isKeyable = isKeyable; + +/** + * Gets the data for `map`. + * + * @private + * @param {Object} map The map to query. + * @param {string} key The reference key. + * @returns {*} Returns the map data. + */ +function getMapData(map, key) { + var data = map.__data__; + return _isKeyable(key) + ? data[typeof key == 'string' ? 'string' : 'hash'] + : data.map; +} + +var _getMapData = getMapData; + +/** + * Removes `key` and its value from the map. + * + * @private + * @name delete + * @memberOf MapCache + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ +function mapCacheDelete(key) { + var result = _getMapData(this, key)['delete'](key); + this.size -= result ? 1 : 0; + return result; +} + +var _mapCacheDelete = mapCacheDelete; + +/** + * Gets the map value for `key`. + * + * @private + * @name get + * @memberOf MapCache + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ +function mapCacheGet(key) { + return _getMapData(this, key).get(key); +} + +var _mapCacheGet = mapCacheGet; + +/** + * Checks if a map value for `key` exists. + * + * @private + * @name has + * @memberOf MapCache + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ +function mapCacheHas(key) { + return _getMapData(this, key).has(key); +} + +var _mapCacheHas = mapCacheHas; + +/** + * Sets the map `key` to `value`. + * + * @private + * @name set + * @memberOf MapCache + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the map cache instance. + */ +function mapCacheSet(key, value) { + var data = _getMapData(this, key), + size = data.size; + + data.set(key, value); + this.size += data.size == size ? 0 : 1; + return this; +} + +var _mapCacheSet = mapCacheSet; + +/** + * Creates a map cache object to store key-value pairs. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ +function MapCache(entries) { + var index = -1, + length = entries == null ? 0 : entries.length; + + this.clear(); + while (++index < length) { + var entry = entries[index]; + this.set(entry[0], entry[1]); + } +} + +// Add methods to `MapCache`. +MapCache.prototype.clear = _mapCacheClear; +MapCache.prototype['delete'] = _mapCacheDelete; +MapCache.prototype.get = _mapCacheGet; +MapCache.prototype.has = _mapCacheHas; +MapCache.prototype.set = _mapCacheSet; + +var _MapCache = MapCache; + +/** Error message constants. */ +var FUNC_ERROR_TEXT = 'Expected a function'; + +/** + * Creates a function that memoizes the result of `func`. If `resolver` is + * provided, it determines the cache key for storing the result based on the + * arguments provided to the memoized function. By default, the first argument + * provided to the memoized function is used as the map cache key. The `func` + * is invoked with the `this` binding of the memoized function. + * + * **Note:** The cache is exposed as the `cache` property on the memoized + * function. Its creation may be customized by replacing the `_.memoize.Cache` + * constructor with one whose instances implement the + * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object) + * method interface of `clear`, `delete`, `get`, `has`, and `set`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Function + * @param {Function} func The function to have its output memoized. + * @param {Function} [resolver] The function to resolve the cache key. + * @returns {Function} Returns the new memoized function. + * @example + * + * var object = { 'a': 1, 'b': 2 }; + * var other = { 'c': 3, 'd': 4 }; + * + * var values = _.memoize(_.values); + * values(object); + * // => [1, 2] + * + * values(other); + * // => [3, 4] + * + * object.a = 2; + * values(object); + * // => [1, 2] + * + * // Modify the result cache. + * values.cache.set(object, ['a', 'b']); + * values(object); + * // => ['a', 'b'] + * + * // Replace `_.memoize.Cache`. + * _.memoize.Cache = WeakMap; + */ +function memoize(func, resolver) { + if (typeof func != 'function' || (resolver != null && typeof resolver != 'function')) { + throw new TypeError(FUNC_ERROR_TEXT); + } + var memoized = function() { + var args = arguments, + key = resolver ? resolver.apply(this, args) : args[0], + cache = memoized.cache; + + if (cache.has(key)) { + return cache.get(key); + } + var result = func.apply(this, args); + memoized.cache = cache.set(key, result) || cache; + return result; + }; + memoized.cache = new (memoize.Cache || _MapCache); + return memoized; +} + +// Expose `MapCache`. +memoize.Cache = _MapCache; + +var memoize_1 = memoize; + +/** Used as the maximum memoize cache size. */ +var MAX_MEMOIZE_SIZE = 500; + +/** + * A specialized version of `_.memoize` which clears the memoized function's + * cache when it exceeds `MAX_MEMOIZE_SIZE`. + * + * @private + * @param {Function} func The function to have its output memoized. + * @returns {Function} Returns the new memoized function. + */ +function memoizeCapped(func) { + var result = memoize_1(func, function(key) { + if (cache.size === MAX_MEMOIZE_SIZE) { + cache.clear(); + } + return key; + }); + + var cache = result.cache; + return result; +} + +var _memoizeCapped = memoizeCapped; + +/** Used to match property names within property paths. */ +var rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g; + +/** Used to match backslashes in property paths. */ +var reEscapeChar = /\\(\\)?/g; + +/** + * Converts `string` to a property path array. + * + * @private + * @param {string} string The string to convert. + * @returns {Array} Returns the property path array. + */ +var stringToPath = _memoizeCapped(function(string) { + var result = []; + if (string.charCodeAt(0) === 46 /* . */) { + result.push(''); + } + string.replace(rePropName, function(match, number, quote, subString) { + result.push(quote ? subString.replace(reEscapeChar, '$1') : (number || match)); + }); + return result; +}); + +var _stringToPath = stringToPath; + +/** Used to convert symbols to primitives and strings. */ +var symbolProto$1 = _Symbol ? _Symbol.prototype : undefined, + symbolToString = symbolProto$1 ? symbolProto$1.toString : undefined; + +/** + * The base implementation of `_.toString` which doesn't convert nullish + * values to empty strings. + * + * @private + * @param {*} value The value to process. + * @returns {string} Returns the string. + */ +function baseToString(value) { + // Exit early for strings to avoid a performance hit in some environments. + if (typeof value == 'string') { + return value; + } + if (isArray_1(value)) { + // Recursively convert values (susceptible to call stack limits). + return _arrayMap(value, baseToString) + ''; + } + if (isSymbol_1(value)) { + return symbolToString ? symbolToString.call(value) : ''; + } + var result = (value + ''); + return (result == '0' && (1 / value) == -Infinity) ? '-0' : result; +} + +var _baseToString = baseToString; + +/** + * Converts `value` to a string. An empty string is returned for `null` + * and `undefined` values. The sign of `-0` is preserved. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {string} Returns the converted string. + * @example + * + * _.toString(null); + * // => '' + * + * _.toString(-0); + * // => '-0' + * + * _.toString([1, 2, 3]); + * // => '1,2,3' + */ +function toString$1(value) { + return value == null ? '' : _baseToString(value); +} + +var toString_1 = toString$1; + +/** + * Casts `value` to a path array if it's not one. + * + * @private + * @param {*} value The value to inspect. + * @param {Object} [object] The object to query keys on. + * @returns {Array} Returns the cast property path array. + */ +function castPath(value, object) { + if (isArray_1(value)) { + return value; + } + return _isKey(value, object) ? [value] : _stringToPath(toString_1(value)); +} + +var _castPath = castPath; + +/** + * Converts `value` to a string key if it's not a string or symbol. + * + * @private + * @param {*} value The value to inspect. + * @returns {string|symbol} Returns the key. + */ +function toKey(value) { + if (typeof value == 'string' || isSymbol_1(value)) { + return value; + } + var result = (value + ''); + return (result == '0' && (1 / value) == -Infinity) ? '-0' : result; +} + +var _toKey = toKey; + +/** + * The base implementation of `_.get` without support for default values. + * + * @private + * @param {Object} object The object to query. + * @param {Array|string} path The path of the property to get. + * @returns {*} Returns the resolved value. + */ +function baseGet(object, path) { + path = _castPath(path, object); + + var index = 0, + length = path.length; + + while (object != null && index < length) { + object = object[_toKey(path[index++])]; + } + return (index && index == length) ? object : undefined; +} + +var _baseGet = baseGet; + +/** + * Removes all key-value entries from the stack. + * + * @private + * @name clear + * @memberOf Stack + */ +function stackClear() { + this.__data__ = new _ListCache; + this.size = 0; +} + +var _stackClear = stackClear; + +/** + * Removes `key` and its value from the stack. + * + * @private + * @name delete + * @memberOf Stack + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ +function stackDelete(key) { + var data = this.__data__, + result = data['delete'](key); + + this.size = data.size; + return result; +} + +var _stackDelete = stackDelete; + +/** + * Gets the stack value for `key`. + * + * @private + * @name get + * @memberOf Stack + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ +function stackGet(key) { + return this.__data__.get(key); +} + +var _stackGet = stackGet; + +/** + * Checks if a stack value for `key` exists. + * + * @private + * @name has + * @memberOf Stack + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ +function stackHas(key) { + return this.__data__.has(key); +} + +var _stackHas = stackHas; + +/** Used as the size to enable large array optimizations. */ +var LARGE_ARRAY_SIZE = 200; + +/** + * Sets the stack `key` to `value`. + * + * @private + * @name set + * @memberOf Stack + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the stack cache instance. + */ +function stackSet(key, value) { + var data = this.__data__; + if (data instanceof _ListCache) { + var pairs = data.__data__; + if (!_Map || (pairs.length < LARGE_ARRAY_SIZE - 1)) { + pairs.push([key, value]); + this.size = ++data.size; + return this; + } + data = this.__data__ = new _MapCache(pairs); + } + data.set(key, value); + this.size = data.size; + return this; +} + +var _stackSet = stackSet; + +/** + * Creates a stack cache object to store key-value pairs. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ +function Stack(entries) { + var data = this.__data__ = new _ListCache(entries); + this.size = data.size; +} + +// Add methods to `Stack`. +Stack.prototype.clear = _stackClear; +Stack.prototype['delete'] = _stackDelete; +Stack.prototype.get = _stackGet; +Stack.prototype.has = _stackHas; +Stack.prototype.set = _stackSet; + +var _Stack = Stack; + +/** Used to stand-in for `undefined` hash values. */ +var HASH_UNDEFINED = '__lodash_hash_undefined__'; + +/** + * Adds `value` to the array cache. + * + * @private + * @name add + * @memberOf SetCache + * @alias push + * @param {*} value The value to cache. + * @returns {Object} Returns the cache instance. + */ +function setCacheAdd(value) { + this.__data__.set(value, HASH_UNDEFINED); + return this; +} + +var _setCacheAdd = setCacheAdd; + +/** + * Checks if `value` is in the array cache. + * + * @private + * @name has + * @memberOf SetCache + * @param {*} value The value to search for. + * @returns {boolean} Returns `true` if `value` is found, else `false`. + */ +function setCacheHas(value) { + return this.__data__.has(value); +} + +var _setCacheHas = setCacheHas; + +/** + * + * Creates an array cache object to store unique values. + * + * @private + * @constructor + * @param {Array} [values] The values to cache. + */ +function SetCache(values) { + var index = -1, + length = values == null ? 0 : values.length; + + this.__data__ = new _MapCache; + while (++index < length) { + this.add(values[index]); + } +} + +// Add methods to `SetCache`. +SetCache.prototype.add = SetCache.prototype.push = _setCacheAdd; +SetCache.prototype.has = _setCacheHas; + +var _SetCache = SetCache; + +/** + * A specialized version of `_.some` for arrays without support for iteratee + * shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {boolean} Returns `true` if any element passes the predicate check, + * else `false`. + */ +function arraySome(array, predicate) { + var index = -1, + length = array == null ? 0 : array.length; + + while (++index < length) { + if (predicate(array[index], index, array)) { + return true; + } + } + return false; +} + +var _arraySome = arraySome; + +/** + * Checks if a `cache` value for `key` exists. + * + * @private + * @param {Object} cache The cache to query. + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ +function cacheHas(cache, key) { + return cache.has(key); +} + +var _cacheHas = cacheHas; + +/** Used to compose bitmasks for value comparisons. */ +var COMPARE_PARTIAL_FLAG$5 = 1, + COMPARE_UNORDERED_FLAG$3 = 2; + +/** + * A specialized version of `baseIsEqualDeep` for arrays with support for + * partial deep comparisons. + * + * @private + * @param {Array} array The array to compare. + * @param {Array} other The other array to compare. + * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. + * @param {Function} customizer The function to customize comparisons. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Object} stack Tracks traversed `array` and `other` objects. + * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`. + */ +function equalArrays(array, other, bitmask, customizer, equalFunc, stack) { + var isPartial = bitmask & COMPARE_PARTIAL_FLAG$5, + arrLength = array.length, + othLength = other.length; + + if (arrLength != othLength && !(isPartial && othLength > arrLength)) { + return false; + } + // Check that cyclic values are equal. + var arrStacked = stack.get(array); + var othStacked = stack.get(other); + if (arrStacked && othStacked) { + return arrStacked == other && othStacked == array; + } + var index = -1, + result = true, + seen = (bitmask & COMPARE_UNORDERED_FLAG$3) ? new _SetCache : undefined; + + stack.set(array, other); + stack.set(other, array); + + // Ignore non-index properties. + while (++index < arrLength) { + var arrValue = array[index], + othValue = other[index]; + + if (customizer) { + var compared = isPartial + ? customizer(othValue, arrValue, index, other, array, stack) + : customizer(arrValue, othValue, index, array, other, stack); + } + if (compared !== undefined) { + if (compared) { + continue; + } + result = false; + break; + } + // Recursively compare arrays (susceptible to call stack limits). + if (seen) { + if (!_arraySome(other, function(othValue, othIndex) { + if (!_cacheHas(seen, othIndex) && + (arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) { + return seen.push(othIndex); + } + })) { + result = false; + break; + } + } else if (!( + arrValue === othValue || + equalFunc(arrValue, othValue, bitmask, customizer, stack) + )) { + result = false; + break; + } + } + stack['delete'](array); + stack['delete'](other); + return result; +} + +var _equalArrays = equalArrays; + +/** Built-in value references. */ +var Uint8Array$1 = _root.Uint8Array; + +var _Uint8Array = Uint8Array$1; + +/** + * Converts `map` to its key-value pairs. + * + * @private + * @param {Object} map The map to convert. + * @returns {Array} Returns the key-value pairs. + */ +function mapToArray(map) { + var index = -1, + result = Array(map.size); + + map.forEach(function(value, key) { + result[++index] = [key, value]; + }); + return result; +} + +var _mapToArray = mapToArray; + +/** + * Converts `set` to an array of its values. + * + * @private + * @param {Object} set The set to convert. + * @returns {Array} Returns the values. + */ +function setToArray(set) { + var index = -1, + result = Array(set.size); + + set.forEach(function(value) { + result[++index] = value; + }); + return result; +} + +var _setToArray = setToArray; + +/** Used to compose bitmasks for value comparisons. */ +var COMPARE_PARTIAL_FLAG$4 = 1, + COMPARE_UNORDERED_FLAG$2 = 2; + +/** `Object#toString` result references. */ +var boolTag$1 = '[object Boolean]', + dateTag = '[object Date]', + errorTag = '[object Error]', + mapTag = '[object Map]', + numberTag = '[object Number]', + regexpTag = '[object RegExp]', + setTag = '[object Set]', + stringTag = '[object String]', + symbolTag = '[object Symbol]'; + +var arrayBufferTag = '[object ArrayBuffer]', + dataViewTag = '[object DataView]'; + +/** Used to convert symbols to primitives and strings. */ +var symbolProto = _Symbol ? _Symbol.prototype : undefined, + symbolValueOf = symbolProto ? symbolProto.valueOf : undefined; + +/** + * A specialized version of `baseIsEqualDeep` for comparing objects of + * the same `toStringTag`. + * + * **Note:** This function only supports comparing values with tags of + * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {string} tag The `toStringTag` of the objects to compare. + * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. + * @param {Function} customizer The function to customize comparisons. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Object} stack Tracks traversed `object` and `other` objects. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ +function equalByTag(object, other, tag, bitmask, customizer, equalFunc, stack) { + switch (tag) { + case dataViewTag: + if ((object.byteLength != other.byteLength) || + (object.byteOffset != other.byteOffset)) { + return false; + } + object = object.buffer; + other = other.buffer; + + case arrayBufferTag: + if ((object.byteLength != other.byteLength) || + !equalFunc(new _Uint8Array(object), new _Uint8Array(other))) { + return false; + } + return true; + + case boolTag$1: + case dateTag: + case numberTag: + // Coerce booleans to `1` or `0` and dates to milliseconds. + // Invalid dates are coerced to `NaN`. + return eq_1(+object, +other); + + case errorTag: + return object.name == other.name && object.message == other.message; + + case regexpTag: + case stringTag: + // Coerce regexes to strings and treat strings, primitives and objects, + // as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring + // for more details. + return object == (other + ''); + + case mapTag: + var convert = _mapToArray; + + case setTag: + var isPartial = bitmask & COMPARE_PARTIAL_FLAG$4; + convert || (convert = _setToArray); + + if (object.size != other.size && !isPartial) { + return false; + } + // Assume cyclic values are equal. + var stacked = stack.get(object); + if (stacked) { + return stacked == other; + } + bitmask |= COMPARE_UNORDERED_FLAG$2; + + // Recursively compare objects (susceptible to call stack limits). + stack.set(object, other); + var result = _equalArrays(convert(object), convert(other), bitmask, customizer, equalFunc, stack); + stack['delete'](object); + return result; + + case symbolTag: + if (symbolValueOf) { + return symbolValueOf.call(object) == symbolValueOf.call(other); + } + } + return false; +} + +var _equalByTag = equalByTag; + +/** + * The base implementation of `getAllKeys` and `getAllKeysIn` which uses + * `keysFunc` and `symbolsFunc` to get the enumerable property names and + * symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @param {Function} keysFunc The function to get the keys of `object`. + * @param {Function} symbolsFunc The function to get the symbols of `object`. + * @returns {Array} Returns the array of property names and symbols. + */ +function baseGetAllKeys(object, keysFunc, symbolsFunc) { + var result = keysFunc(object); + return isArray_1(object) ? result : _arrayPush(result, symbolsFunc(object)); +} + +var _baseGetAllKeys = baseGetAllKeys; + +/** + * A specialized version of `_.filter` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {Array} Returns the new filtered array. + */ +function arrayFilter(array, predicate) { + var index = -1, + length = array == null ? 0 : array.length, + resIndex = 0, + result = []; + + while (++index < length) { + var value = array[index]; + if (predicate(value, index, array)) { + result[resIndex++] = value; + } + } + return result; +} + +var _arrayFilter = arrayFilter; + +/** + * This method returns a new empty array. + * + * @static + * @memberOf _ + * @since 4.13.0 + * @category Util + * @returns {Array} Returns the new empty array. + * @example + * + * var arrays = _.times(2, _.stubArray); + * + * console.log(arrays); + * // => [[], []] + * + * console.log(arrays[0] === arrays[1]); + * // => false + */ +function stubArray() { + return []; +} + +var stubArray_1 = stubArray; + +/** Used for built-in method references. */ +var objectProto$3 = Object.prototype; + +/** Built-in value references. */ +var propertyIsEnumerable = objectProto$3.propertyIsEnumerable; + +/* Built-in method references for those with the same name as other `lodash` methods. */ +var nativeGetSymbols = Object.getOwnPropertySymbols; + +/** + * Creates an array of the own enumerable symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of symbols. + */ +var getSymbols = !nativeGetSymbols ? stubArray_1 : function(object) { + if (object == null) { + return []; + } + object = Object(object); + return _arrayFilter(nativeGetSymbols(object), function(symbol) { + return propertyIsEnumerable.call(object, symbol); + }); +}; + +var _getSymbols = getSymbols; + +/** + * The base implementation of `_.times` without support for iteratee shorthands + * or max array length checks. + * + * @private + * @param {number} n The number of times to invoke `iteratee`. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the array of results. + */ +function baseTimes(n, iteratee) { + var index = -1, + result = Array(n); + + while (++index < n) { + result[index] = iteratee(index); + } + return result; +} + +var _baseTimes = baseTimes; + +/** Used as references for various `Number` constants. */ +var MAX_SAFE_INTEGER = 9007199254740991; + +/** Used to detect unsigned integer values. */ +var reIsUint = /^(?:0|[1-9]\d*)$/; + +/** + * Checks if `value` is a valid array-like index. + * + * @private + * @param {*} value The value to check. + * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. + * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. + */ +function isIndex(value, length) { + var type = typeof value; + length = length == null ? MAX_SAFE_INTEGER : length; + + return !!length && + (type == 'number' || + (type != 'symbol' && reIsUint.test(value))) && + (value > -1 && value % 1 == 0 && value < length); +} + +var _isIndex = isIndex; + +/** Used for built-in method references. */ +var objectProto$2 = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty$3 = objectProto$2.hasOwnProperty; + +/** + * Creates an array of the enumerable property names of the array-like `value`. + * + * @private + * @param {*} value The value to query. + * @param {boolean} inherited Specify returning inherited property names. + * @returns {Array} Returns the array of property names. + */ +function arrayLikeKeys(value, inherited) { + var isArr = isArray_1(value), + isArg = !isArr && isArguments_1(value), + isBuff = !isArr && !isArg && isBuffer_1(value), + isType = !isArr && !isArg && !isBuff && isTypedArray_1(value), + skipIndexes = isArr || isArg || isBuff || isType, + result = skipIndexes ? _baseTimes(value.length, String) : [], + length = result.length; + + for (var key in value) { + if ((inherited || hasOwnProperty$3.call(value, key)) && + !(skipIndexes && ( + // Safari 9 has enumerable `arguments.length` in strict mode. + key == 'length' || + // Node.js 0.10 has enumerable non-index properties on buffers. + (isBuff && (key == 'offset' || key == 'parent')) || + // PhantomJS 2 has enumerable non-index properties on typed arrays. + (isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset')) || + // Skip index properties. + _isIndex(key, length) + ))) { + result.push(key); + } + } + return result; +} + +var _arrayLikeKeys = arrayLikeKeys; + +/** + * Creates an array of the own enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. See the + * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) + * for more details. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keys(new Foo); + * // => ['a', 'b'] (iteration order is not guaranteed) + * + * _.keys('hi'); + * // => ['0', '1'] + */ +function keys(object) { + return isArrayLike_1(object) ? _arrayLikeKeys(object) : _baseKeys(object); +} + +var keys_1 = keys; + +/** + * Creates an array of own enumerable property names and symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names and symbols. + */ +function getAllKeys(object) { + return _baseGetAllKeys(object, keys_1, _getSymbols); +} + +var _getAllKeys = getAllKeys; + +/** Used to compose bitmasks for value comparisons. */ +var COMPARE_PARTIAL_FLAG$3 = 1; + +/** Used for built-in method references. */ +var objectProto$1 = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty$2 = objectProto$1.hasOwnProperty; + +/** + * A specialized version of `baseIsEqualDeep` for objects with support for + * partial deep comparisons. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. + * @param {Function} customizer The function to customize comparisons. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Object} stack Tracks traversed `object` and `other` objects. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ +function equalObjects(object, other, bitmask, customizer, equalFunc, stack) { + var isPartial = bitmask & COMPARE_PARTIAL_FLAG$3, + objProps = _getAllKeys(object), + objLength = objProps.length, + othProps = _getAllKeys(other), + othLength = othProps.length; + + if (objLength != othLength && !isPartial) { + return false; + } + var index = objLength; + while (index--) { + var key = objProps[index]; + if (!(isPartial ? key in other : hasOwnProperty$2.call(other, key))) { + return false; + } + } + // Check that cyclic values are equal. + var objStacked = stack.get(object); + var othStacked = stack.get(other); + if (objStacked && othStacked) { + return objStacked == other && othStacked == object; + } + var result = true; + stack.set(object, other); + stack.set(other, object); + + var skipCtor = isPartial; + while (++index < objLength) { + key = objProps[index]; + var objValue = object[key], + othValue = other[key]; + + if (customizer) { + var compared = isPartial + ? customizer(othValue, objValue, key, other, object, stack) + : customizer(objValue, othValue, key, object, other, stack); + } + // Recursively compare objects (susceptible to call stack limits). + if (!(compared === undefined + ? (objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack)) + : compared + )) { + result = false; + break; + } + skipCtor || (skipCtor = key == 'constructor'); + } + if (result && !skipCtor) { + var objCtor = object.constructor, + othCtor = other.constructor; + + // Non `Object` object instances with different constructors are not equal. + if (objCtor != othCtor && + ('constructor' in object && 'constructor' in other) && + !(typeof objCtor == 'function' && objCtor instanceof objCtor && + typeof othCtor == 'function' && othCtor instanceof othCtor)) { + result = false; + } + } + stack['delete'](object); + stack['delete'](other); + return result; +} + +var _equalObjects = equalObjects; + +/** Used to compose bitmasks for value comparisons. */ +var COMPARE_PARTIAL_FLAG$2 = 1; + +/** `Object#toString` result references. */ +var argsTag = '[object Arguments]', + arrayTag = '[object Array]', + objectTag = '[object Object]'; + +/** Used for built-in method references. */ +var objectProto = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty$1 = objectProto.hasOwnProperty; + +/** + * A specialized version of `baseIsEqual` for arrays and objects which performs + * deep comparisons and tracks traversed objects enabling objects with circular + * references to be compared. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. + * @param {Function} customizer The function to customize comparisons. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Object} [stack] Tracks traversed `object` and `other` objects. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ +function baseIsEqualDeep(object, other, bitmask, customizer, equalFunc, stack) { + var objIsArr = isArray_1(object), + othIsArr = isArray_1(other), + objTag = objIsArr ? arrayTag : _getTag(object), + othTag = othIsArr ? arrayTag : _getTag(other); + + objTag = objTag == argsTag ? objectTag : objTag; + othTag = othTag == argsTag ? objectTag : othTag; + + var objIsObj = objTag == objectTag, + othIsObj = othTag == objectTag, + isSameTag = objTag == othTag; + + if (isSameTag && isBuffer_1(object)) { + if (!isBuffer_1(other)) { + return false; + } + objIsArr = true; + objIsObj = false; + } + if (isSameTag && !objIsObj) { + stack || (stack = new _Stack); + return (objIsArr || isTypedArray_1(object)) + ? _equalArrays(object, other, bitmask, customizer, equalFunc, stack) + : _equalByTag(object, other, objTag, bitmask, customizer, equalFunc, stack); + } + if (!(bitmask & COMPARE_PARTIAL_FLAG$2)) { + var objIsWrapped = objIsObj && hasOwnProperty$1.call(object, '__wrapped__'), + othIsWrapped = othIsObj && hasOwnProperty$1.call(other, '__wrapped__'); + + if (objIsWrapped || othIsWrapped) { + var objUnwrapped = objIsWrapped ? object.value() : object, + othUnwrapped = othIsWrapped ? other.value() : other; + + stack || (stack = new _Stack); + return equalFunc(objUnwrapped, othUnwrapped, bitmask, customizer, stack); + } + } + if (!isSameTag) { + return false; + } + stack || (stack = new _Stack); + return _equalObjects(object, other, bitmask, customizer, equalFunc, stack); +} + +var _baseIsEqualDeep = baseIsEqualDeep; + +/** + * The base implementation of `_.isEqual` which supports partial comparisons + * and tracks traversed objects. + * + * @private + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @param {boolean} bitmask The bitmask flags. + * 1 - Unordered comparison + * 2 - Partial comparison + * @param {Function} [customizer] The function to customize comparisons. + * @param {Object} [stack] Tracks traversed `value` and `other` objects. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + */ +function baseIsEqual(value, other, bitmask, customizer, stack) { + if (value === other) { + return true; + } + if (value == null || other == null || (!isObjectLike_1(value) && !isObjectLike_1(other))) { + return value !== value && other !== other; + } + return _baseIsEqualDeep(value, other, bitmask, customizer, baseIsEqual, stack); +} + +var _baseIsEqual = baseIsEqual; + +/** Used to compose bitmasks for value comparisons. */ +var COMPARE_PARTIAL_FLAG$1 = 1, + COMPARE_UNORDERED_FLAG$1 = 2; + +/** + * The base implementation of `_.isMatch` without support for iteratee shorthands. + * + * @private + * @param {Object} object The object to inspect. + * @param {Object} source The object of property values to match. + * @param {Array} matchData The property names, values, and compare flags to match. + * @param {Function} [customizer] The function to customize comparisons. + * @returns {boolean} Returns `true` if `object` is a match, else `false`. + */ +function baseIsMatch(object, source, matchData, customizer) { + var index = matchData.length, + length = index, + noCustomizer = !customizer; + + if (object == null) { + return !length; + } + object = Object(object); + while (index--) { + var data = matchData[index]; + if ((noCustomizer && data[2]) + ? data[1] !== object[data[0]] + : !(data[0] in object) + ) { + return false; + } + } + while (++index < length) { + data = matchData[index]; + var key = data[0], + objValue = object[key], + srcValue = data[1]; + + if (noCustomizer && data[2]) { + if (objValue === undefined && !(key in object)) { + return false; + } + } else { + var stack = new _Stack; + if (customizer) { + var result = customizer(objValue, srcValue, key, object, source, stack); + } + if (!(result === undefined + ? _baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG$1 | COMPARE_UNORDERED_FLAG$1, customizer, stack) + : result + )) { + return false; + } + } + } + return true; +} + +var _baseIsMatch = baseIsMatch; + +/** + * Checks if `value` is suitable for strict equality comparisons, i.e. `===`. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` if suitable for strict + * equality comparisons, else `false`. + */ +function isStrictComparable(value) { + return value === value && !isObject_1(value); +} + +var _isStrictComparable = isStrictComparable; + +/** + * Gets the property names, values, and compare flags of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the match data of `object`. + */ +function getMatchData(object) { + var result = keys_1(object), + length = result.length; + + while (length--) { + var key = result[length], + value = object[key]; + + result[length] = [key, value, _isStrictComparable(value)]; + } + return result; +} + +var _getMatchData = getMatchData; + +/** + * A specialized version of `matchesProperty` for source values suitable + * for strict equality comparisons, i.e. `===`. + * + * @private + * @param {string} key The key of the property to get. + * @param {*} srcValue The value to match. + * @returns {Function} Returns the new spec function. + */ +function matchesStrictComparable(key, srcValue) { + return function(object) { + if (object == null) { + return false; + } + return object[key] === srcValue && + (srcValue !== undefined || (key in Object(object))); + }; +} + +var _matchesStrictComparable = matchesStrictComparable; + +/** + * The base implementation of `_.matches` which doesn't clone `source`. + * + * @private + * @param {Object} source The object of property values to match. + * @returns {Function} Returns the new spec function. + */ +function baseMatches(source) { + var matchData = _getMatchData(source); + if (matchData.length == 1 && matchData[0][2]) { + return _matchesStrictComparable(matchData[0][0], matchData[0][1]); + } + return function(object) { + return object === source || _baseIsMatch(object, source, matchData); + }; +} + +var _baseMatches = baseMatches; + +/** + * Gets the value at `path` of `object`. If the resolved value is + * `undefined`, the `defaultValue` is returned in its place. + * + * @static + * @memberOf _ + * @since 3.7.0 + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path of the property to get. + * @param {*} [defaultValue] The value returned for `undefined` resolved values. + * @returns {*} Returns the resolved value. + * @example + * + * var object = { 'a': [{ 'b': { 'c': 3 } }] }; + * + * _.get(object, 'a[0].b.c'); + * // => 3 + * + * _.get(object, ['a', '0', 'b', 'c']); + * // => 3 + * + * _.get(object, 'a.b.c', 'default'); + * // => 'default' + */ +function get(object, path, defaultValue) { + var result = object == null ? undefined : _baseGet(object, path); + return result === undefined ? defaultValue : result; +} + +var get_1 = get; + +/** + * The base implementation of `_.hasIn` without support for deep paths. + * + * @private + * @param {Object} [object] The object to query. + * @param {Array|string} key The key to check. + * @returns {boolean} Returns `true` if `key` exists, else `false`. + */ +function baseHasIn(object, key) { + return object != null && key in Object(object); +} + +var _baseHasIn = baseHasIn; + +/** + * Checks if `path` exists on `object`. + * + * @private + * @param {Object} object The object to query. + * @param {Array|string} path The path to check. + * @param {Function} hasFunc The function to check properties. + * @returns {boolean} Returns `true` if `path` exists, else `false`. + */ +function hasPath(object, path, hasFunc) { + path = _castPath(path, object); + + var index = -1, + length = path.length, + result = false; + + while (++index < length) { + var key = _toKey(path[index]); + if (!(result = object != null && hasFunc(object, key))) { + break; + } + object = object[key]; + } + if (result || ++index != length) { + return result; + } + length = object == null ? 0 : object.length; + return !!length && isLength_1(length) && _isIndex(key, length) && + (isArray_1(object) || isArguments_1(object)); +} + +var _hasPath = hasPath; + +/** + * Checks if `path` is a direct or inherited property of `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path to check. + * @returns {boolean} Returns `true` if `path` exists, else `false`. + * @example + * + * var object = _.create({ 'a': _.create({ 'b': 2 }) }); + * + * _.hasIn(object, 'a'); + * // => true + * + * _.hasIn(object, 'a.b'); + * // => true + * + * _.hasIn(object, ['a', 'b']); + * // => true + * + * _.hasIn(object, 'b'); + * // => false + */ +function hasIn(object, path) { + return object != null && _hasPath(object, path, _baseHasIn); +} + +var hasIn_1 = hasIn; + +/** Used to compose bitmasks for value comparisons. */ +var COMPARE_PARTIAL_FLAG = 1, + COMPARE_UNORDERED_FLAG = 2; + +/** + * The base implementation of `_.matchesProperty` which doesn't clone `srcValue`. + * + * @private + * @param {string} path The path of the property to get. + * @param {*} srcValue The value to match. + * @returns {Function} Returns the new spec function. + */ +function baseMatchesProperty(path, srcValue) { + if (_isKey(path) && _isStrictComparable(srcValue)) { + return _matchesStrictComparable(_toKey(path), srcValue); + } + return function(object) { + var objValue = get_1(object, path); + return (objValue === undefined && objValue === srcValue) + ? hasIn_1(object, path) + : _baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG); + }; +} + +var _baseMatchesProperty = baseMatchesProperty; + +/** + * This method returns the first argument it receives. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Util + * @param {*} value Any value. + * @returns {*} Returns `value`. + * @example + * + * var object = { 'a': 1 }; + * + * console.log(_.identity(object) === object); + * // => true + */ +function identity(value) { + return value; +} + +var identity_1 = identity; + +/** + * The base implementation of `_.property` without support for deep paths. + * + * @private + * @param {string} key The key of the property to get. + * @returns {Function} Returns the new accessor function. + */ +function baseProperty(key) { + return function(object) { + return object == null ? undefined : object[key]; + }; +} + +var _baseProperty = baseProperty; + +/** + * A specialized version of `baseProperty` which supports deep paths. + * + * @private + * @param {Array|string} path The path of the property to get. + * @returns {Function} Returns the new accessor function. + */ +function basePropertyDeep(path) { + return function(object) { + return _baseGet(object, path); + }; +} + +var _basePropertyDeep = basePropertyDeep; + +/** + * Creates a function that returns the value at `path` of a given object. + * + * @static + * @memberOf _ + * @since 2.4.0 + * @category Util + * @param {Array|string} path The path of the property to get. + * @returns {Function} Returns the new accessor function. + * @example + * + * var objects = [ + * { 'a': { 'b': 2 } }, + * { 'a': { 'b': 1 } } + * ]; + * + * _.map(objects, _.property('a.b')); + * // => [2, 1] + * + * _.map(_.sortBy(objects, _.property(['a', 'b'])), 'a.b'); + * // => [1, 2] + */ +function property(path) { + return _isKey(path) ? _baseProperty(_toKey(path)) : _basePropertyDeep(path); +} + +var property_1 = property; + +/** + * The base implementation of `_.iteratee`. + * + * @private + * @param {*} [value=_.identity] The value to convert to an iteratee. + * @returns {Function} Returns the iteratee. + */ +function baseIteratee(value) { + // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9. + // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details. + if (typeof value == 'function') { + return value; + } + if (value == null) { + return identity_1; + } + if (typeof value == 'object') { + return isArray_1(value) + ? _baseMatchesProperty(value[0], value[1]) + : _baseMatches(value); + } + return property_1(value); +} + +var _baseIteratee = baseIteratee; + +/** + * Creates a base function for methods like `_.forIn` and `_.forOwn`. + * + * @private + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new base function. + */ +function createBaseFor(fromRight) { + return function(object, iteratee, keysFunc) { + var index = -1, + iterable = Object(object), + props = keysFunc(object), + length = props.length; + + while (length--) { + var key = props[fromRight ? length : ++index]; + if (iteratee(iterable[key], key, iterable) === false) { + break; + } + } + return object; + }; +} + +var _createBaseFor = createBaseFor; + +/** + * The base implementation of `baseForOwn` which iterates over `object` + * properties returned by `keysFunc` and invokes `iteratee` for each property. + * Iteratee functions may exit iteration early by explicitly returning `false`. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {Function} keysFunc The function to get the keys of `object`. + * @returns {Object} Returns `object`. + */ +var baseFor = _createBaseFor(); + +var _baseFor = baseFor; + +/** + * The base implementation of `_.forOwn` without support for iteratee shorthands. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Object} Returns `object`. + */ +function baseForOwn(object, iteratee) { + return object && _baseFor(object, iteratee, keys_1); +} + +var _baseForOwn = baseForOwn; + +/** + * Creates a `baseEach` or `baseEachRight` function. + * + * @private + * @param {Function} eachFunc The function to iterate over a collection. + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new base function. + */ +function createBaseEach(eachFunc, fromRight) { + return function(collection, iteratee) { + if (collection == null) { + return collection; + } + if (!isArrayLike_1(collection)) { + return eachFunc(collection, iteratee); + } + var length = collection.length, + index = fromRight ? length : -1, + iterable = Object(collection); + + while ((fromRight ? index-- : ++index < length)) { + if (iteratee(iterable[index], index, iterable) === false) { + break; + } + } + return collection; + }; +} + +var _createBaseEach = createBaseEach; + +/** + * The base implementation of `_.forEach` without support for iteratee shorthands. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array|Object} Returns `collection`. + */ +var baseEach = _createBaseEach(_baseForOwn); + +var _baseEach = baseEach; + +/** + * The base implementation of `_.map` without support for iteratee shorthands. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the new mapped array. + */ +function baseMap(collection, iteratee) { + var index = -1, + result = isArrayLike_1(collection) ? Array(collection.length) : []; + + _baseEach(collection, function(value, key, collection) { + result[++index] = iteratee(value, key, collection); + }); + return result; +} + +var _baseMap = baseMap; + +/** + * The base implementation of `_.sortBy` which uses `comparer` to define the + * sort order of `array` and replaces criteria objects with their corresponding + * values. + * + * @private + * @param {Array} array The array to sort. + * @param {Function} comparer The function to define sort order. + * @returns {Array} Returns `array`. + */ +function baseSortBy(array, comparer) { + var length = array.length; + + array.sort(comparer); + while (length--) { + array[length] = array[length].value; + } + return array; +} + +var _baseSortBy = baseSortBy; + +/** + * Compares values to sort them in ascending order. + * + * @private + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {number} Returns the sort order indicator for `value`. + */ +function compareAscending(value, other) { + if (value !== other) { + var valIsDefined = value !== undefined, + valIsNull = value === null, + valIsReflexive = value === value, + valIsSymbol = isSymbol_1(value); + + var othIsDefined = other !== undefined, + othIsNull = other === null, + othIsReflexive = other === other, + othIsSymbol = isSymbol_1(other); + + if ((!othIsNull && !othIsSymbol && !valIsSymbol && value > other) || + (valIsSymbol && othIsDefined && othIsReflexive && !othIsNull && !othIsSymbol) || + (valIsNull && othIsDefined && othIsReflexive) || + (!valIsDefined && othIsReflexive) || + !valIsReflexive) { + return 1; + } + if ((!valIsNull && !valIsSymbol && !othIsSymbol && value < other) || + (othIsSymbol && valIsDefined && valIsReflexive && !valIsNull && !valIsSymbol) || + (othIsNull && valIsDefined && valIsReflexive) || + (!othIsDefined && valIsReflexive) || + !othIsReflexive) { + return -1; + } + } + return 0; +} + +var _compareAscending = compareAscending; + +/** + * Used by `_.orderBy` to compare multiple properties of a value to another + * and stable sort them. + * + * If `orders` is unspecified, all values are sorted in ascending order. Otherwise, + * specify an order of "desc" for descending or "asc" for ascending sort order + * of corresponding values. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {boolean[]|string[]} orders The order to sort by for each property. + * @returns {number} Returns the sort order indicator for `object`. + */ +function compareMultiple(object, other, orders) { + var index = -1, + objCriteria = object.criteria, + othCriteria = other.criteria, + length = objCriteria.length, + ordersLength = orders.length; + + while (++index < length) { + var result = _compareAscending(objCriteria[index], othCriteria[index]); + if (result) { + if (index >= ordersLength) { + return result; + } + var order = orders[index]; + return result * (order == 'desc' ? -1 : 1); + } + } + // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications + // that causes it, under certain circumstances, to provide the same value for + // `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247 + // for more details. + // + // This also ensures a stable sort in V8 and other engines. + // See https://bugs.chromium.org/p/v8/issues/detail?id=90 for more details. + return object.index - other.index; +} + +var _compareMultiple = compareMultiple; + +/** + * The base implementation of `_.orderBy` without param guards. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by. + * @param {string[]} orders The sort orders of `iteratees`. + * @returns {Array} Returns the new sorted array. + */ +function baseOrderBy(collection, iteratees, orders) { + if (iteratees.length) { + iteratees = _arrayMap(iteratees, function(iteratee) { + if (isArray_1(iteratee)) { + return function(value) { + return _baseGet(value, iteratee.length === 1 ? iteratee[0] : iteratee); + }; + } + return iteratee; + }); + } else { + iteratees = [identity_1]; + } + + var index = -1; + iteratees = _arrayMap(iteratees, _baseUnary(_baseIteratee)); + + var result = _baseMap(collection, function(value, key, collection) { + var criteria = _arrayMap(iteratees, function(iteratee) { + return iteratee(value); + }); + return { 'criteria': criteria, 'index': ++index, 'value': value }; + }); + + return _baseSortBy(result, function(object, other) { + return _compareMultiple(object, other, orders); + }); +} + +var _baseOrderBy = baseOrderBy; + +/** + * A faster alternative to `Function#apply`, this function invokes `func` + * with the `this` binding of `thisArg` and the arguments of `args`. + * + * @private + * @param {Function} func The function to invoke. + * @param {*} thisArg The `this` binding of `func`. + * @param {Array} args The arguments to invoke `func` with. + * @returns {*} Returns the result of `func`. + */ +function apply(func, thisArg, args) { + switch (args.length) { + case 0: return func.call(thisArg); + case 1: return func.call(thisArg, args[0]); + case 2: return func.call(thisArg, args[0], args[1]); + case 3: return func.call(thisArg, args[0], args[1], args[2]); + } + return func.apply(thisArg, args); +} + +var _apply = apply; + +/* Built-in method references for those with the same name as other `lodash` methods. */ +var nativeMax = Math.max; + +/** + * A specialized version of `baseRest` which transforms the rest array. + * + * @private + * @param {Function} func The function to apply a rest parameter to. + * @param {number} [start=func.length-1] The start position of the rest parameter. + * @param {Function} transform The rest array transform. + * @returns {Function} Returns the new function. + */ +function overRest(func, start, transform) { + start = nativeMax(start === undefined ? (func.length - 1) : start, 0); + return function() { + var args = arguments, + index = -1, + length = nativeMax(args.length - start, 0), + array = Array(length); + + while (++index < length) { + array[index] = args[start + index]; + } + index = -1; + var otherArgs = Array(start + 1); + while (++index < start) { + otherArgs[index] = args[index]; + } + otherArgs[start] = transform(array); + return _apply(func, this, otherArgs); + }; +} + +var _overRest = overRest; + +/** + * Creates a function that returns `value`. + * + * @static + * @memberOf _ + * @since 2.4.0 + * @category Util + * @param {*} value The value to return from the new function. + * @returns {Function} Returns the new constant function. + * @example + * + * var objects = _.times(2, _.constant({ 'a': 1 })); + * + * console.log(objects); + * // => [{ 'a': 1 }, { 'a': 1 }] + * + * console.log(objects[0] === objects[1]); + * // => true + */ +function constant(value) { + return function() { + return value; + }; +} + +var constant_1 = constant; + +var defineProperty = (function() { + try { + var func = _getNative(Object, 'defineProperty'); + func({}, '', {}); + return func; + } catch (e) {} +}()); + +var _defineProperty = defineProperty; + +/** + * The base implementation of `setToString` without support for hot loop shorting. + * + * @private + * @param {Function} func The function to modify. + * @param {Function} string The `toString` result. + * @returns {Function} Returns `func`. + */ +var baseSetToString = !_defineProperty ? identity_1 : function(func, string) { + return _defineProperty(func, 'toString', { + 'configurable': true, + 'enumerable': false, + 'value': constant_1(string), + 'writable': true + }); +}; + +var _baseSetToString = baseSetToString; + +/** Used to detect hot functions by number of calls within a span of milliseconds. */ +var HOT_COUNT = 800, + HOT_SPAN = 16; + +/* Built-in method references for those with the same name as other `lodash` methods. */ +var nativeNow = Date.now; + +/** + * Creates a function that'll short out and invoke `identity` instead + * of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN` + * milliseconds. + * + * @private + * @param {Function} func The function to restrict. + * @returns {Function} Returns the new shortable function. + */ +function shortOut(func) { + var count = 0, + lastCalled = 0; + + return function() { + var stamp = nativeNow(), + remaining = HOT_SPAN - (stamp - lastCalled); + + lastCalled = stamp; + if (remaining > 0) { + if (++count >= HOT_COUNT) { + return arguments[0]; + } + } else { + count = 0; + } + return func.apply(undefined, arguments); + }; +} + +var _shortOut = shortOut; + +/** + * Sets the `toString` method of `func` to return `string`. + * + * @private + * @param {Function} func The function to modify. + * @param {Function} string The `toString` result. + * @returns {Function} Returns `func`. + */ +var setToString = _shortOut(_baseSetToString); + +var _setToString = setToString; + +/** + * The base implementation of `_.rest` which doesn't validate or coerce arguments. + * + * @private + * @param {Function} func The function to apply a rest parameter to. + * @param {number} [start=func.length-1] The start position of the rest parameter. + * @returns {Function} Returns the new function. + */ +function baseRest(func, start) { + return _setToString(_overRest(func, start, identity_1), func + ''); +} + +var _baseRest = baseRest; + +/** + * Checks if the given arguments are from an iteratee call. + * + * @private + * @param {*} value The potential iteratee value argument. + * @param {*} index The potential iteratee index or key argument. + * @param {*} object The potential iteratee object argument. + * @returns {boolean} Returns `true` if the arguments are from an iteratee call, + * else `false`. + */ +function isIterateeCall(value, index, object) { + if (!isObject_1(object)) { + return false; + } + var type = typeof index; + if (type == 'number' + ? (isArrayLike_1(object) && _isIndex(index, object.length)) + : (type == 'string' && index in object) + ) { + return eq_1(object[index], value); + } + return false; +} + +var _isIterateeCall = isIterateeCall; + +/** + * Creates an array of elements, sorted in ascending order by the results of + * running each element in a collection thru each iteratee. This method + * performs a stable sort, that is, it preserves the original sort order of + * equal elements. The iteratees are invoked with one argument: (value). + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {...(Function|Function[])} [iteratees=[_.identity]] + * The iteratees to sort by. + * @returns {Array} Returns the new sorted array. + * @example + * + * var users = [ + * { 'user': 'fred', 'age': 48 }, + * { 'user': 'barney', 'age': 36 }, + * { 'user': 'fred', 'age': 30 }, + * { 'user': 'barney', 'age': 34 } + * ]; + * + * _.sortBy(users, [function(o) { return o.user; }]); + * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 30]] + * + * _.sortBy(users, ['user', 'age']); + * // => objects for [['barney', 34], ['barney', 36], ['fred', 30], ['fred', 48]] + */ +var sortBy = _baseRest(function(collection, iteratees) { + if (collection == null) { + return []; + } + var length = iteratees.length; + if (length > 1 && _isIterateeCall(collection, iteratees[0], iteratees[1])) { + iteratees = []; + } else if (length > 2 && _isIterateeCall(iteratees[0], iteratees[1], iteratees[2])) { + iteratees = [iteratees[0]]; + } + return _baseOrderBy(collection, _baseFlatten(iteratees, 1), []); +}); + +var sortBy_1 = sortBy; + +/** + * Configuration for custom action validation rules. + * Defines allowed positions, metadata IDs, data model IDs, and fields for each target + * type. + * + */ +const customActionValidationConfig = { + [CustomActionTarget.LIVEBOARD]: { + positions: [CustomActionsPosition.PRIMARY, CustomActionsPosition.MENU], + allowedMetadataIds: ['liveboardIds'], + allowedDataModelIds: [], + allowedFields: ['name', 'id', 'position', 'target', 'metadataIds', 'orgIds', 'groupIds'], + }, + [CustomActionTarget.VIZ]: { + positions: [CustomActionsPosition.MENU, CustomActionsPosition.PRIMARY, CustomActionsPosition.CONTEXTMENU], + allowedMetadataIds: ['liveboardIds', 'vizIds', 'answerIds'], + allowedDataModelIds: ['modelIds', 'modelColumnNames'], + allowedFields: ['name', 'id', 'position', 'target', 'metadataIds', 'orgIds', 'groupIds', 'dataModelIds'], + }, + [CustomActionTarget.ANSWER]: { + positions: [CustomActionsPosition.MENU, CustomActionsPosition.PRIMARY, CustomActionsPosition.CONTEXTMENU], + allowedMetadataIds: ['answerIds'], + allowedDataModelIds: ['modelIds', 'modelColumnNames'], + allowedFields: ['name', 'id', 'position', 'target', 'metadataIds', 'orgIds', 'groupIds', 'dataModelIds'], + }, + [CustomActionTarget.SPOTTER]: { + positions: [CustomActionsPosition.MENU, CustomActionsPosition.CONTEXTMENU], + allowedMetadataIds: [], + allowedDataModelIds: ['modelIds'], + allowedFields: ['name', 'id', 'position', 'target', 'orgIds', 'groupIds', 'dataModelIds'], + }, +}; +/** + * Validates a single custom action based on its target type + * @param action - The custom action to validate + * @param primaryActionsPerTarget - Map to track primary actions per target + * @returns CustomActionValidation with isValid flag and reason string + * + * @hidden + */ +const validateCustomAction = (action, primaryActionsPerTarget) => { + const { id: actionId, target: targetType, position, metadataIds, dataModelIds } = action; + // Check if target type is supported + if (!customActionValidationConfig[targetType]) { + const errorMessage = CUSTOM_ACTIONS_ERROR_MESSAGE.UNSUPPORTED_TARGET(actionId, targetType); + return { isValid: false, errors: [errorMessage] }; + } + const config = customActionValidationConfig[targetType]; + const errors = []; + // Validate position + if (!arrayIncludesString(config.positions, position)) { + const supportedPositions = config.positions.join(', '); + errors.push(CUSTOM_ACTIONS_ERROR_MESSAGE.INVALID_POSITION(position, targetType, supportedPositions)); + } + // Validate metadata IDs + if (metadataIds) { + const invalidMetadataIds = Object.keys(metadataIds).filter((key) => !arrayIncludesString(config.allowedMetadataIds, key)); + if (invalidMetadataIds.length > 0) { + const supportedMetadataIds = config.allowedMetadataIds.length > 0 ? config.allowedMetadataIds.join(', ') : 'none'; + errors.push(CUSTOM_ACTIONS_ERROR_MESSAGE.INVALID_METADATA_IDS(targetType, invalidMetadataIds, supportedMetadataIds)); + } + } + // Validate data model IDs + if (dataModelIds) { + const invalidDataModelIds = Object.keys(dataModelIds).filter((key) => !arrayIncludesString(config.allowedDataModelIds, key)); + if (invalidDataModelIds.length > 0) { + const supportedDataModelIds = config.allowedDataModelIds.length > 0 ? config.allowedDataModelIds.join(', ') : 'none'; + errors.push(CUSTOM_ACTIONS_ERROR_MESSAGE.INVALID_DATA_MODEL_IDS(targetType, invalidDataModelIds, supportedDataModelIds)); + } + } + // Validate allowed fields + const actionKeys = Object.keys(action); + const invalidFields = actionKeys.filter((key) => !arrayIncludesString(config.allowedFields, key)); + if (invalidFields.length > 0) { + const supportedFields = config.allowedFields.join(', '); + errors.push(CUSTOM_ACTIONS_ERROR_MESSAGE.INVALID_FIELDS(targetType, invalidFields, supportedFields)); + } + return { + isValid: errors.length === 0, + errors, + }; +}; +/** + * Validates basic action structure and required fields + * @param action - The action to validate + * @returns Object containing validation result and missing fields + * + * @hidden + */ +const validateActionStructure = (action) => { + if (!action || typeof action !== 'object') { + return { isValid: false, missingFields: [] }; + } + // Check for all missing required fields + const missingFields = ['id', 'name', 'target', 'position'].filter(field => !action[field]); + return { isValid: missingFields.length === 0, missingFields }; +}; +/** + * Checks for duplicate IDs among actions + * @param actions - Array of actions to check + * @returns Object containing filtered actions and duplicate errors + * + * @hidden + */ +const filterDuplicateIds = (actions) => { + const idMap = actions.reduce((map, action) => { + const list = map.get(action.id) || []; + list.push(action); + map.set(action.id, list); + return map; + }, new Map()); + const { actions: actionsWithUniqueIds, errors } = Array.from(idMap.entries()).reduce((acc, [id, actionsWithSameId]) => { + if (actionsWithSameId.length === 1) { + acc.actions.push(actionsWithSameId[0]); + } + else { + // Keep the first action and add error for duplicates + acc.actions.push(actionsWithSameId[0]); + const duplicateNames = actionsWithSameId.slice(1).map(action => action.name); + acc.errors.push(CUSTOM_ACTIONS_ERROR_MESSAGE.DUPLICATE_IDS(id, duplicateNames, actionsWithSameId[0].name)); + } + return acc; + }, { actions: [], errors: [] }); + return { actions: actionsWithUniqueIds, errors }; +}; +/** + * Validates and processes custom actions + * @param customActions - Array of custom actions to validate + * @returns Object containing valid actions and any validation errors + */ +const getCustomActions = (customActions) => { + const errors = []; + if (!customActions || !Array.isArray(customActions)) { + return { actions: [], errors: [] }; + } + // Step 1: Handle invalid actions first (null, undefined, missing required + // fields) + const validActions = customActions.filter(action => { + const validation = validateActionStructure(action); + if (!validation.isValid) { + if (!action || typeof action !== 'object') { + errors.push(CUSTOM_ACTIONS_ERROR_MESSAGE.INVALID_ACTION_OBJECT); + } + else { + errors.push(CUSTOM_ACTIONS_ERROR_MESSAGE.MISSING_REQUIRED_FIELDS(action.id, validation.missingFields)); + } + return false; + } + return true; + }); + // Step 2: Check for duplicate IDs among valid actions + const { actions: actionsWithUniqueIds, errors: duplicateErrors } = filterDuplicateIds(validActions); + // Add duplicate errors to the errors array + duplicateErrors.forEach(error => errors.push(error)); + // Step 3: Validate actions with unique IDs + const finalValidActions = []; + actionsWithUniqueIds.forEach((action) => { + const { isValid, errors: validationErrors } = validateCustomAction(action); + validationErrors.forEach(error => errors.push(error)); + if (isValid) { + finalValidActions.push(action); + } + }); + // Step 4: Collect warnings for long custom action names + const MAX_ACTION_NAME_LENGTH = 30; + const warnings = finalValidActions + .filter(action => action.name.length > MAX_ACTION_NAME_LENGTH) + .map(action => `Custom action name '${action.name}' exceeds ${MAX_ACTION_NAME_LENGTH} characters. This may cause display or truncation issues in the UI.`); + if (warnings.length > 0) { + logger$3.warn(warnings); + } + const sortedActions = sortBy_1(finalValidActions, (a) => a.name.toLocaleLowerCase()); + return { + actions: sortedActions, + errors: errors, + }; +}; + +/** + * Copyright (c) 2023 + * + * Utilities related to reading configuration objects + * @summary Config-related utils + * @author Ayon Ghosh + */ +const urlRegex = new RegExp([ + '(^(https?:)//)?', + '(([^:/?#]*)(?::([0-9]+))?)', + '(/{0,1}[^?#]*)', + '(\\?[^#]*|)', + '(#.*|)$', // hash +].join('')); +/** + * Parse and construct the ThoughtSpot hostname or IP address + * from the embed configuration object. + * @param config + */ +const getThoughtSpotHost = (config) => { + if (!config.thoughtSpotHost) { + throw new Error(ERROR_MESSAGE.INVALID_THOUGHTSPOT_HOST); + } + const urlParts = config.thoughtSpotHost.match(urlRegex); + if (!urlParts) { + throw new Error(ERROR_MESSAGE.INVALID_THOUGHTSPOT_HOST); + } + const protocol = urlParts[2] || window.location.protocol; + const host = urlParts[3]; + let path = urlParts[6]; + // Lose the trailing / if any + if (path.charAt(path.length - 1) === '/') { + path = path.substring(0, path.length - 1); + } + // const urlParams = urlParts[7]; + // const hash = urlParts[8]; + return `${protocol}//${host}${path}`; +}; +const getV2BasePath = (config) => { + if (config.basepath) { + return config.basepath; + } + const tsHost = getThoughtSpotHost(config); + // This is to handle when e2e's. Search is run on pods for + // comp-blink-test-pipeline with baseUrl=https://localhost:8443. + // This is to handle when the developer is developing in their local + // environment. + if (tsHost.includes('://localhost') && !tsHost.includes(':8443')) { + return ''; + } + return 'v2'; +}; +/** + * It is a good idea to keep URLs under 2000 chars. + * If this is ever breached, since we pass view configuration through + * URL params, we would like to log a warning. + * Reference: https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers + */ +const URL_MAX_LENGTH = 2000; +/** + * The default CSS dimensions of the embedded app + */ +const DEFAULT_EMBED_WIDTH = '100%'; +const DEFAULT_EMBED_HEIGHT = '100%'; + +var Config = { + DEBUG: false, + LIB_VERSION: '2.47.0' +}; + +// since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file +var window$1; +if (typeof(window) === 'undefined') { + var loc = { + hostname: '' + }; + window$1 = { + navigator: { userAgent: '' }, + document: { + location: loc, + referrer: '' + }, + screen: { width: 0, height: 0 }, + location: loc + }; +} else { + window$1 = window; +} + +/* + * Saved references to long variable names, so that closure compiler can + * minimize file size. + */ + +var ArrayProto = Array.prototype; +var FuncProto = Function.prototype; +var ObjProto = Object.prototype; +var slice = ArrayProto.slice; +var toString = ObjProto.toString; +var hasOwnProperty = ObjProto.hasOwnProperty; +var windowConsole = window$1.console; +var navigator = window$1.navigator; +var document$1 = window$1.document; +var windowOpera = window$1.opera; +var screen = window$1.screen; +var userAgent = navigator.userAgent; +var nativeBind = FuncProto.bind; +var nativeForEach = ArrayProto.forEach; +var nativeIndexOf = ArrayProto.indexOf; +var nativeMap = ArrayProto.map; +var nativeIsArray = Array.isArray; +var breaker = {}; +var _ = { + trim: function(str) { + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Polyfill + return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); + } +}; + +// Console override +var console$1 = { + /** @type {function(...*)} */ + log: function() { + if (Config.DEBUG && !_.isUndefined(windowConsole) && windowConsole) { + try { + windowConsole.log.apply(windowConsole, arguments); + } catch (err) { + _.each(arguments, function(arg) { + windowConsole.log(arg); + }); + } + } + }, + /** @type {function(...*)} */ + warn: function() { + if (Config.DEBUG && !_.isUndefined(windowConsole) && windowConsole) { + var args = ['Mixpanel warning:'].concat(_.toArray(arguments)); + try { + windowConsole.warn.apply(windowConsole, args); + } catch (err) { + _.each(args, function(arg) { + windowConsole.warn(arg); + }); + } + } + }, + /** @type {function(...*)} */ + error: function() { + if (Config.DEBUG && !_.isUndefined(windowConsole) && windowConsole) { + var args = ['Mixpanel error:'].concat(_.toArray(arguments)); + try { + windowConsole.error.apply(windowConsole, args); + } catch (err) { + _.each(args, function(arg) { + windowConsole.error(arg); + }); + } + } + }, + /** @type {function(...*)} */ + critical: function() { + if (!_.isUndefined(windowConsole) && windowConsole) { + var args = ['Mixpanel error:'].concat(_.toArray(arguments)); + try { + windowConsole.error.apply(windowConsole, args); + } catch (err) { + _.each(args, function(arg) { + windowConsole.error(arg); + }); + } + } + } +}; + +var log_func_with_prefix = function(func, prefix) { + return function() { + arguments[0] = '[' + prefix + '] ' + arguments[0]; + return func.apply(console$1, arguments); + }; +}; +var console_with_prefix = function(prefix) { + return { + log: log_func_with_prefix(console$1.log, prefix), + error: log_func_with_prefix(console$1.error, prefix), + critical: log_func_with_prefix(console$1.critical, prefix) + }; +}; + + +// UNDERSCORE +// Embed part of the Underscore Library +_.bind = function(func, context) { + var args, bound; + if (nativeBind && func.bind === nativeBind) { + return nativeBind.apply(func, slice.call(arguments, 1)); + } + if (!_.isFunction(func)) { + throw new TypeError(); + } + args = slice.call(arguments, 2); + bound = function() { + if (!(this instanceof bound)) { + return func.apply(context, args.concat(slice.call(arguments))); + } + var ctor = {}; + ctor.prototype = func.prototype; + var self = new ctor(); + ctor.prototype = null; + var result = func.apply(self, args.concat(slice.call(arguments))); + if (Object(result) === result) { + return result; + } + return self; + }; + return bound; +}; + +/** + * @param {*=} obj + * @param {function(...*)=} iterator + * @param {Object=} context + */ +_.each = function(obj, iterator, context) { + if (obj === null || obj === undefined) { + return; + } + if (nativeForEach && obj.forEach === nativeForEach) { + obj.forEach(iterator, context); + } else if (obj.length === +obj.length) { + for (var i = 0, l = obj.length; i < l; i++) { + if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) { + return; + } + } + } else { + for (var key in obj) { + if (hasOwnProperty.call(obj, key)) { + if (iterator.call(context, obj[key], key, obj) === breaker) { + return; + } + } + } + } +}; + +_.extend = function(obj) { + _.each(slice.call(arguments, 1), function(source) { + for (var prop in source) { + if (source[prop] !== void 0) { + obj[prop] = source[prop]; + } + } + }); + return obj; +}; + +_.isArray = nativeIsArray || function(obj) { + return toString.call(obj) === '[object Array]'; +}; + +// from a comment on http://dbj.org/dbj/?p=286 +// fails on only one very rare and deliberate custom object: +// var bomb = { toString : undefined, valueOf: function(o) { return "function BOMBA!"; }}; +_.isFunction = function(f) { + try { + return /^\s*\bfunction\b/.test(f); + } catch (x) { + return false; + } +}; + +_.isArguments = function(obj) { + return !!(obj && hasOwnProperty.call(obj, 'callee')); +}; + +_.toArray = function(iterable) { + if (!iterable) { + return []; + } + if (iterable.toArray) { + return iterable.toArray(); + } + if (_.isArray(iterable)) { + return slice.call(iterable); + } + if (_.isArguments(iterable)) { + return slice.call(iterable); + } + return _.values(iterable); +}; + +_.map = function(arr, callback, context) { + if (nativeMap && arr.map === nativeMap) { + return arr.map(callback, context); + } else { + var results = []; + _.each(arr, function(item) { + results.push(callback.call(context, item)); + }); + return results; + } +}; + +_.keys = function(obj) { + var results = []; + if (obj === null) { + return results; + } + _.each(obj, function(value, key) { + results[results.length] = key; + }); + return results; +}; + +_.values = function(obj) { + var results = []; + if (obj === null) { + return results; + } + _.each(obj, function(value) { + results[results.length] = value; + }); + return results; +}; + +_.include = function(obj, target) { + var found = false; + if (obj === null) { + return found; + } + if (nativeIndexOf && obj.indexOf === nativeIndexOf) { + return obj.indexOf(target) != -1; + } + _.each(obj, function(value) { + if (found || (found = (value === target))) { + return breaker; + } + }); + return found; +}; + +_.includes = function(str, needle) { + return str.indexOf(needle) !== -1; +}; + +// Underscore Addons +_.inherit = function(subclass, superclass) { + subclass.prototype = new superclass(); + subclass.prototype.constructor = subclass; + subclass.superclass = superclass.prototype; + return subclass; +}; + +_.isObject = function(obj) { + return (obj === Object(obj) && !_.isArray(obj)); +}; + +_.isEmptyObject = function(obj) { + if (_.isObject(obj)) { + for (var key in obj) { + if (hasOwnProperty.call(obj, key)) { + return false; + } + } + return true; + } + return false; +}; + +_.isUndefined = function(obj) { + return obj === void 0; +}; + +_.isString = function(obj) { + return toString.call(obj) == '[object String]'; +}; + +_.isDate = function(obj) { + return toString.call(obj) == '[object Date]'; +}; + +_.isNumber = function(obj) { + return toString.call(obj) == '[object Number]'; +}; + +_.isElement = function(obj) { + return !!(obj && obj.nodeType === 1); +}; + +_.encodeDates = function(obj) { + _.each(obj, function(v, k) { + if (_.isDate(v)) { + obj[k] = _.formatDate(v); + } else if (_.isObject(v)) { + obj[k] = _.encodeDates(v); // recurse + } + }); + return obj; +}; + +_.timestamp = function() { + Date.now = Date.now || function() { + return +new Date; + }; + return Date.now(); +}; + +_.formatDate = function(d) { + // YYYY-MM-DDTHH:MM:SS in UTC + function pad(n) { + return n < 10 ? '0' + n : n; + } + return d.getUTCFullYear() + '-' + + pad(d.getUTCMonth() + 1) + '-' + + pad(d.getUTCDate()) + 'T' + + pad(d.getUTCHours()) + ':' + + pad(d.getUTCMinutes()) + ':' + + pad(d.getUTCSeconds()); +}; + +_.strip_empty_properties = function(p) { + var ret = {}; + _.each(p, function(v, k) { + if (_.isString(v) && v.length > 0) { + ret[k] = v; + } + }); + return ret; +}; + +/* + * this function returns a copy of object after truncating it. If + * passed an Array or Object it will iterate through obj and + * truncate all the values recursively. + */ +_.truncate = function(obj, length) { + var ret; + + if (typeof(obj) === 'string') { + ret = obj.slice(0, length); + } else if (_.isArray(obj)) { + ret = []; + _.each(obj, function(val) { + ret.push(_.truncate(val, length)); + }); + } else if (_.isObject(obj)) { + ret = {}; + _.each(obj, function(val, key) { + ret[key] = _.truncate(val, length); + }); + } else { + ret = obj; + } + + return ret; +}; + +_.JSONEncode = (function() { + return function(mixed_val) { + var value = mixed_val; + var quote = function(string) { + var escapable = /[\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; // eslint-disable-line no-control-regex + var meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"': '\\"', + '\\': '\\\\' + }; + + escapable.lastIndex = 0; + return escapable.test(string) ? + '"' + string.replace(escapable, function(a) { + var c = meta[a]; + return typeof c === 'string' ? c : + '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : + '"' + string + '"'; + }; + + var str = function(key, holder) { + var gap = ''; + var indent = ' '; + var i = 0; // The loop counter. + var k = ''; // The member key. + var v = ''; // The member value. + var length = 0; + var mind = gap; + var partial = []; + var value = holder[key]; + + // If the value has a toJSON method, call it to obtain a replacement value. + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + + // What happens next depends on the value's type. + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + // JSON numbers must be finite. Encode non-finite numbers as null. + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + // If the value is a boolean or null, convert it to a string. Note: + // typeof null does not produce 'null'. The case is included here in + // the remote chance that this gets fixed someday. + + return String(value); + + case 'object': + // If the type is 'object', we might be dealing with an object or an array or + // null. + // Due to a specification blunder in ECMAScript, typeof null is 'object', + // so watch out for that case. + if (!value) { + return 'null'; + } + + // Make an array to hold the partial results of stringifying this object value. + gap += indent; + partial = []; + + // Is the value an array? + if (toString.apply(value) === '[object Array]') { + // The value is an array. Stringify every element. Use null as a placeholder + // for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + + // Join all of the elements together, separated with commas, and wrap them in + // brackets. + v = partial.length === 0 ? '[]' : + gap ? '[\n' + gap + + partial.join(',\n' + gap) + '\n' + + mind + ']' : + '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + + // Iterate through all of the keys in the object. + for (k in value) { + if (hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + + // Join all of the member texts together, separated with commas, + // and wrap them in braces. + v = partial.length === 0 ? '{}' : + gap ? '{' + partial.join(',') + '' + + mind + '}' : '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + }; + + // Make a fake root object containing our value under the key of ''. + // Return the result of stringifying the value. + return str('', { + '': value + }); + }; +})(); + +/** + * From https://github.com/douglascrockford/JSON-js/blob/master/json_parse.js + * Slightly modified to throw a real Error rather than a POJO + */ +_.JSONDecode = (function() { + var at, // The index of the current character + ch, // The current character + escapee = { + '"': '"', + '\\': '\\', + '/': '/', + 'b': '\b', + 'f': '\f', + 'n': '\n', + 'r': '\r', + 't': '\t' + }, + text, + error = function(m) { + var e = new SyntaxError(m); + e.at = at; + e.text = text; + throw e; + }, + next = function(c) { + // If a c parameter is provided, verify that it matches the current character. + if (c && c !== ch) { + error('Expected \'' + c + '\' instead of \'' + ch + '\''); + } + // Get the next character. When there are no more characters, + // return the empty string. + ch = text.charAt(at); + at += 1; + return ch; + }, + number = function() { + // Parse a number value. + var number, + string = ''; + + if (ch === '-') { + string = '-'; + next('-'); + } + while (ch >= '0' && ch <= '9') { + string += ch; + next(); + } + if (ch === '.') { + string += '.'; + while (next() && ch >= '0' && ch <= '9') { + string += ch; + } + } + if (ch === 'e' || ch === 'E') { + string += ch; + next(); + if (ch === '-' || ch === '+') { + string += ch; + next(); + } + while (ch >= '0' && ch <= '9') { + string += ch; + next(); + } + } + number = +string; + if (!isFinite(number)) { + error('Bad number'); + } else { + return number; + } + }, + + string = function() { + // Parse a string value. + var hex, + i, + string = '', + uffff; + // When parsing for string values, we must look for " and \ characters. + if (ch === '"') { + while (next()) { + if (ch === '"') { + next(); + return string; + } + if (ch === '\\') { + next(); + if (ch === 'u') { + uffff = 0; + for (i = 0; i < 4; i += 1) { + hex = parseInt(next(), 16); + if (!isFinite(hex)) { + break; + } + uffff = uffff * 16 + hex; + } + string += String.fromCharCode(uffff); + } else if (typeof escapee[ch] === 'string') { + string += escapee[ch]; + } else { + break; + } + } else { + string += ch; + } + } + } + error('Bad string'); + }, + white = function() { + // Skip whitespace. + while (ch && ch <= ' ') { + next(); + } + }, + word = function() { + // true, false, or null. + switch (ch) { + case 't': + next('t'); + next('r'); + next('u'); + next('e'); + return true; + case 'f': + next('f'); + next('a'); + next('l'); + next('s'); + next('e'); + return false; + case 'n': + next('n'); + next('u'); + next('l'); + next('l'); + return null; + } + error('Unexpected "' + ch + '"'); + }, + value, // Placeholder for the value function. + array = function() { + // Parse an array value. + var array = []; + + if (ch === '[') { + next('['); + white(); + if (ch === ']') { + next(']'); + return array; // empty array + } + while (ch) { + array.push(value()); + white(); + if (ch === ']') { + next(']'); + return array; + } + next(','); + white(); + } + } + error('Bad array'); + }, + object = function() { + // Parse an object value. + var key, + object = {}; + + if (ch === '{') { + next('{'); + white(); + if (ch === '}') { + next('}'); + return object; // empty object + } + while (ch) { + key = string(); + white(); + next(':'); + if (Object.hasOwnProperty.call(object, key)) { + error('Duplicate key "' + key + '"'); + } + object[key] = value(); + white(); + if (ch === '}') { + next('}'); + return object; + } + next(','); + white(); + } + } + error('Bad object'); + }; + + value = function() { + // Parse a JSON value. It could be an object, an array, a string, + // a number, or a word. + white(); + switch (ch) { + case '{': + return object(); + case '[': + return array(); + case '"': + return string(); + case '-': + return number(); + default: + return ch >= '0' && ch <= '9' ? number() : word(); + } + }; + + // Return the json_parse function. It will have access to all of the + // above functions and variables. + return function(source) { + var result; + + text = source; + at = 0; + ch = ' '; + result = value(); + white(); + if (ch) { + error('Syntax error'); + } + + return result; + }; +})(); + +_.base64Encode = function(data) { + var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, + ac = 0, + enc = '', + tmp_arr = []; + + if (!data) { + return data; + } + + data = _.utf8Encode(data); + + do { // pack three octets into four hexets + o1 = data.charCodeAt(i++); + o2 = data.charCodeAt(i++); + o3 = data.charCodeAt(i++); + + bits = o1 << 16 | o2 << 8 | o3; + + h1 = bits >> 18 & 0x3f; + h2 = bits >> 12 & 0x3f; + h3 = bits >> 6 & 0x3f; + h4 = bits & 0x3f; + + // use hexets to index into b64, and append result to encoded string + tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4); + } while (i < data.length); + + enc = tmp_arr.join(''); + + switch (data.length % 3) { + case 1: + enc = enc.slice(0, -2) + '=='; + break; + case 2: + enc = enc.slice(0, -1) + '='; + break; + } + + return enc; +}; + +_.utf8Encode = function(string) { + string = (string + '').replace(/\r\n/g, '\n').replace(/\r/g, '\n'); + + var utftext = '', + start, + end; + var stringl = 0, + n; + + start = end = 0; + stringl = string.length; + + for (n = 0; n < stringl; n++) { + var c1 = string.charCodeAt(n); + var enc = null; + + if (c1 < 128) { + end++; + } else if ((c1 > 127) && (c1 < 2048)) { + enc = String.fromCharCode((c1 >> 6) | 192, (c1 & 63) | 128); + } else { + enc = String.fromCharCode((c1 >> 12) | 224, ((c1 >> 6) & 63) | 128, (c1 & 63) | 128); + } + if (enc !== null) { + if (end > start) { + utftext += string.substring(start, end); + } + utftext += enc; + start = end = n + 1; + } + } + + if (end > start) { + utftext += string.substring(start, string.length); + } + + return utftext; +}; + +_.UUID = (function() { + + // Time-based entropy + var T = function() { + var time = 1 * new Date(); // cross-browser version of Date.now() + var ticks; + if (window$1.performance && window$1.performance.now) { + ticks = window$1.performance.now(); + } else { + // fall back to busy loop + ticks = 0; + + // this while loop figures how many browser ticks go by + // before 1*new Date() returns a new number, ie the amount + // of ticks that go by per millisecond + while (time == 1 * new Date()) { + ticks++; + } + } + return time.toString(16) + Math.floor(ticks).toString(16); + }; + + // Math.Random entropy + var R = function() { + return Math.random().toString(16).replace('.', ''); + }; + + // User agent entropy + // This function takes the user agent string, and then xors + // together each sequence of 8 bytes. This produces a final + // sequence of 8 bytes which it returns as hex. + var UA = function() { + var ua = userAgent, + i, ch, buffer = [], + ret = 0; + + function xor(result, byte_array) { + var j, tmp = 0; + for (j = 0; j < byte_array.length; j++) { + tmp |= (buffer[j] << j * 8); + } + return result ^ tmp; + } + + for (i = 0; i < ua.length; i++) { + ch = ua.charCodeAt(i); + buffer.unshift(ch & 0xFF); + if (buffer.length >= 4) { + ret = xor(ret, buffer); + buffer = []; + } + } + + if (buffer.length > 0) { + ret = xor(ret, buffer); + } + + return ret.toString(16); + }; + + return function() { + var se = (screen.height * screen.width).toString(16); + return (T() + '-' + R() + '-' + UA() + '-' + se + '-' + T()); + }; +})(); + +// _.isBlockedUA() +// This is to block various web spiders from executing our JS and +// sending false tracking data +var BLOCKED_UA_STRS = [ + 'ahrefsbot', + 'baiduspider', + 'bingbot', + 'bingpreview', + 'facebookexternal', + 'petalbot', + 'pinterest', + 'screaming frog', + 'yahoo! slurp', + 'yandexbot', + + // a whole bunch of goog-specific crawlers + // https://developers.google.com/search/docs/advanced/crawling/overview-google-crawlers + 'adsbot-google', + 'apis-google', + 'duplexweb-google', + 'feedfetcher-google', + 'google favicon', + 'google web preview', + 'google-read-aloud', + 'googlebot', + 'googleweblight', + 'mediapartners-google', + 'storebot-google' +]; +_.isBlockedUA = function(ua) { + var i; + ua = ua.toLowerCase(); + for (i = 0; i < BLOCKED_UA_STRS.length; i++) { + if (ua.indexOf(BLOCKED_UA_STRS[i]) !== -1) { + return true; + } + } + return false; +}; + +/** + * @param {Object=} formdata + * @param {string=} arg_separator + */ +_.HTTPBuildQuery = function(formdata, arg_separator) { + var use_val, use_key, tmp_arr = []; + + if (_.isUndefined(arg_separator)) { + arg_separator = '&'; + } + + _.each(formdata, function(val, key) { + use_val = encodeURIComponent(val.toString()); + use_key = encodeURIComponent(key); + tmp_arr[tmp_arr.length] = use_key + '=' + use_val; + }); + + return tmp_arr.join(arg_separator); +}; + +_.getQueryParam = function(url, param) { + // Expects a raw URL + + param = param.replace(/[[]/, '\\[').replace(/[\]]/, '\\]'); + var regexS = '[\\?&]' + param + '=([^&#]*)', + regex = new RegExp(regexS), + results = regex.exec(url); + if (results === null || (results && typeof(results[1]) !== 'string' && results[1].length)) { + return ''; + } else { + var result = results[1]; + try { + result = decodeURIComponent(result); + } catch(err) { + console$1.error('Skipping decoding for malformed query param: ' + result); + } + return result.replace(/\+/g, ' '); + } +}; + + +// _.cookie +// Methods partially borrowed from quirksmode.org/js/cookies.html +_.cookie = { + get: function(name) { + var nameEQ = name + '='; + var ca = document$1.cookie.split(';'); + for (var i = 0; i < ca.length; i++) { + var c = ca[i]; + while (c.charAt(0) == ' ') { + c = c.substring(1, c.length); + } + if (c.indexOf(nameEQ) === 0) { + return decodeURIComponent(c.substring(nameEQ.length, c.length)); + } + } + return null; + }, + + parse: function(name) { + var cookie; + try { + cookie = _.JSONDecode(_.cookie.get(name)) || {}; + } catch (err) { + // noop + } + return cookie; + }, + + set_seconds: function(name, value, seconds, is_cross_subdomain, is_secure, is_cross_site, domain_override) { + var cdomain = '', + expires = '', + secure = ''; + + if (domain_override) { + cdomain = '; domain=' + domain_override; + } else if (is_cross_subdomain) { + var domain = extract_domain(document$1.location.hostname); + cdomain = domain ? '; domain=.' + domain : ''; + } + + if (seconds) { + var date = new Date(); + date.setTime(date.getTime() + (seconds * 1000)); + expires = '; expires=' + date.toGMTString(); + } + + if (is_cross_site) { + is_secure = true; + secure = '; SameSite=None'; + } + if (is_secure) { + secure += '; secure'; + } + + document$1.cookie = name + '=' + encodeURIComponent(value) + expires + '; path=/' + cdomain + secure; + }, + + set: function(name, value, days, is_cross_subdomain, is_secure, is_cross_site, domain_override) { + var cdomain = '', expires = '', secure = ''; + + if (domain_override) { + cdomain = '; domain=' + domain_override; + } else if (is_cross_subdomain) { + var domain = extract_domain(document$1.location.hostname); + cdomain = domain ? '; domain=.' + domain : ''; + } + + if (days) { + var date = new Date(); + date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + expires = '; expires=' + date.toGMTString(); + } + + if (is_cross_site) { + is_secure = true; + secure = '; SameSite=None'; + } + if (is_secure) { + secure += '; secure'; + } + + var new_cookie_val = name + '=' + encodeURIComponent(value) + expires + '; path=/' + cdomain + secure; + document$1.cookie = new_cookie_val; + return new_cookie_val; + }, + + remove: function(name, is_cross_subdomain, domain_override) { + _.cookie.set(name, '', -1, is_cross_subdomain, false, false, domain_override); + } +}; + +var _localStorageSupported = null; +var localStorageSupported = function(storage, forceCheck) { + if (_localStorageSupported !== null && !forceCheck) { + return _localStorageSupported; + } + + var supported = true; + try { + storage = storage || window.localStorage; + var key = '__mplss_' + cheap_guid(8), + val = 'xyz'; + storage.setItem(key, val); + if (storage.getItem(key) !== val) { + supported = false; + } + storage.removeItem(key); + } catch (err) { + supported = false; + } + + _localStorageSupported = supported; + return supported; +}; + +// _.localStorage +_.localStorage = { + is_supported: function(force_check) { + var supported = localStorageSupported(null, force_check); + if (!supported) { + console$1.error('localStorage unsupported; falling back to cookie store'); + } + return supported; + }, + + error: function(msg) { + console$1.error('localStorage error: ' + msg); + }, + + get: function(name) { + try { + return window.localStorage.getItem(name); + } catch (err) { + _.localStorage.error(err); + } + return null; + }, + + parse: function(name) { + try { + return _.JSONDecode(_.localStorage.get(name)) || {}; + } catch (err) { + // noop + } + return null; + }, + + set: function(name, value) { + try { + window.localStorage.setItem(name, value); + } catch (err) { + _.localStorage.error(err); + } + }, + + remove: function(name) { + try { + window.localStorage.removeItem(name); + } catch (err) { + _.localStorage.error(err); + } + } +}; + +_.register_event = (function() { + // written by Dean Edwards, 2005 + // with input from Tino Zijdel - crisp@xs4all.nl + // with input from Carl Sverre - mail@carlsverre.com + // with input from Mixpanel + // http://dean.edwards.name/weblog/2005/10/add-event/ + // https://gist.github.com/1930440 + + /** + * @param {Object} element + * @param {string} type + * @param {function(...*)} handler + * @param {boolean=} oldSchool + * @param {boolean=} useCapture + */ + var register_event = function(element, type, handler, oldSchool, useCapture) { + if (!element) { + console$1.error('No valid element provided to register_event'); + return; + } + + if (element.addEventListener && !oldSchool) { + element.addEventListener(type, handler, !!useCapture); + } else { + var ontype = 'on' + type; + var old_handler = element[ontype]; // can be undefined + element[ontype] = makeHandler(element, handler, old_handler); + } + }; + + function makeHandler(element, new_handler, old_handlers) { + var handler = function(event) { + event = event || fixEvent(window.event); + + // this basically happens in firefox whenever another script + // overwrites the onload callback and doesn't pass the event + // object to previously defined callbacks. All the browsers + // that don't define window.event implement addEventListener + // so the dom_loaded handler will still be fired as usual. + if (!event) { + return undefined; + } + + var ret = true; + var old_result, new_result; + + if (_.isFunction(old_handlers)) { + old_result = old_handlers(event); + } + new_result = new_handler.call(element, event); + + if ((false === old_result) || (false === new_result)) { + ret = false; + } + + return ret; + }; + + return handler; + } + + function fixEvent(event) { + if (event) { + event.preventDefault = fixEvent.preventDefault; + event.stopPropagation = fixEvent.stopPropagation; + } + return event; + } + fixEvent.preventDefault = function() { + this.returnValue = false; + }; + fixEvent.stopPropagation = function() { + this.cancelBubble = true; + }; + + return register_event; +})(); + + +var TOKEN_MATCH_REGEX = new RegExp('^(\\w*)\\[(\\w+)([=~\\|\\^\\$\\*]?)=?"?([^\\]"]*)"?\\]$'); + +_.dom_query = (function() { + /* document.getElementsBySelector(selector) + - returns an array of element objects from the current document + matching the CSS selector. Selectors can contain element names, + class names and ids and can be nested. For example: + + elements = document.getElementsBySelector('div#main p a.external') + + Will return an array of all 'a' elements with 'external' in their + class attribute that are contained inside 'p' elements that are + contained inside the 'div' element which has id="main" + + New in version 0.4: Support for CSS2 and CSS3 attribute selectors: + See http://www.w3.org/TR/css3-selectors/#attribute-selectors + + Version 0.4 - Simon Willison, March 25th 2003 + -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows + -- Opera 7 fails + + Version 0.5 - Carl Sverre, Jan 7th 2013 + -- Now uses jQuery-esque `hasClass` for testing class name + equality. This fixes a bug related to '-' characters being + considered not part of a 'word' in regex. + */ + + function getAllChildren(e) { + // Returns all children of element. Workaround required for IE5/Windows. Ugh. + return e.all ? e.all : e.getElementsByTagName('*'); + } + + var bad_whitespace = /[\t\r\n]/g; + + function hasClass(elem, selector) { + var className = ' ' + selector + ' '; + return ((' ' + elem.className + ' ').replace(bad_whitespace, ' ').indexOf(className) >= 0); + } + + function getElementsBySelector(selector) { + // Attempt to fail gracefully in lesser browsers + if (!document$1.getElementsByTagName) { + return []; + } + // Split selector in to tokens + var tokens = selector.split(' '); + var token, bits, tagName, found, foundCount, i, j, k, elements, currentContextIndex; + var currentContext = [document$1]; + for (i = 0; i < tokens.length; i++) { + token = tokens[i].replace(/^\s+/, '').replace(/\s+$/, ''); + if (token.indexOf('#') > -1) { + // Token is an ID selector + bits = token.split('#'); + tagName = bits[0]; + var id = bits[1]; + var element = document$1.getElementById(id); + if (!element || (tagName && element.nodeName.toLowerCase() != tagName)) { + // element not found or tag with that ID not found, return false + return []; + } + // Set currentContext to contain just this element + currentContext = [element]; + continue; // Skip to next token + } + if (token.indexOf('.') > -1) { + // Token contains a class selector + bits = token.split('.'); + tagName = bits[0]; + var className = bits[1]; + if (!tagName) { + tagName = '*'; + } + // Get elements matching tag, filter them for class selector + found = []; + foundCount = 0; + for (j = 0; j < currentContext.length; j++) { + if (tagName == '*') { + elements = getAllChildren(currentContext[j]); + } else { + elements = currentContext[j].getElementsByTagName(tagName); + } + for (k = 0; k < elements.length; k++) { + found[foundCount++] = elements[k]; + } + } + currentContext = []; + currentContextIndex = 0; + for (j = 0; j < found.length; j++) { + if (found[j].className && + _.isString(found[j].className) && // some SVG elements have classNames which are not strings + hasClass(found[j], className) + ) { + currentContext[currentContextIndex++] = found[j]; + } + } + continue; // Skip to next token + } + // Code to deal with attribute selectors + var token_match = token.match(TOKEN_MATCH_REGEX); + if (token_match) { + tagName = token_match[1]; + var attrName = token_match[2]; + var attrOperator = token_match[3]; + var attrValue = token_match[4]; + if (!tagName) { + tagName = '*'; + } + // Grab all of the tagName elements within current context + found = []; + foundCount = 0; + for (j = 0; j < currentContext.length; j++) { + if (tagName == '*') { + elements = getAllChildren(currentContext[j]); + } else { + elements = currentContext[j].getElementsByTagName(tagName); + } + for (k = 0; k < elements.length; k++) { + found[foundCount++] = elements[k]; + } + } + currentContext = []; + currentContextIndex = 0; + var checkFunction; // This function will be used to filter the elements + switch (attrOperator) { + case '=': // Equality + checkFunction = function(e) { + return (e.getAttribute(attrName) == attrValue); + }; + break; + case '~': // Match one of space seperated words + checkFunction = function(e) { + return (e.getAttribute(attrName).match(new RegExp('\\b' + attrValue + '\\b'))); + }; + break; + case '|': // Match start with value followed by optional hyphen + checkFunction = function(e) { + return (e.getAttribute(attrName).match(new RegExp('^' + attrValue + '-?'))); + }; + break; + case '^': // Match starts with value + checkFunction = function(e) { + return (e.getAttribute(attrName).indexOf(attrValue) === 0); + }; + break; + case '$': // Match ends with value - fails with "Warning" in Opera 7 + checkFunction = function(e) { + return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); + }; + break; + case '*': // Match ends with value + checkFunction = function(e) { + return (e.getAttribute(attrName).indexOf(attrValue) > -1); + }; + break; + default: + // Just test for existence of attribute + checkFunction = function(e) { + return e.getAttribute(attrName); + }; + } + currentContext = []; + currentContextIndex = 0; + for (j = 0; j < found.length; j++) { + if (checkFunction(found[j])) { + currentContext[currentContextIndex++] = found[j]; + } + } + // alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue); + continue; // Skip to next token + } + // If we get here, token is JUST an element (not a class or ID selector) + tagName = token; + found = []; + foundCount = 0; + for (j = 0; j < currentContext.length; j++) { + elements = currentContext[j].getElementsByTagName(tagName); + for (k = 0; k < elements.length; k++) { + found[foundCount++] = elements[k]; + } + } + currentContext = found; + } + return currentContext; + } + + return function(query) { + if (_.isElement(query)) { + return [query]; + } else if (_.isObject(query) && !_.isUndefined(query.length)) { + return query; + } else { + return getElementsBySelector.call(this, query); + } + }; +})(); + +var CAMPAIGN_KEYWORDS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term']; +var CLICK_IDS = ['dclid', 'fbclid', 'gclid', 'ko_click_id', 'li_fat_id', 'msclkid', 'ttclid', 'twclid', 'wbraid']; + +_.info = { + campaignParams: function(default_value) { + var kw = '', + params = {}; + _.each(CAMPAIGN_KEYWORDS, function(kwkey) { + kw = _.getQueryParam(document$1.URL, kwkey); + if (kw.length) { + params[kwkey] = kw; + } else if (default_value !== undefined) { + params[kwkey] = default_value; + } + }); + + return params; + }, + + clickParams: function() { + var id = '', + params = {}; + _.each(CLICK_IDS, function(idkey) { + id = _.getQueryParam(document$1.URL, idkey); + if (id.length) { + params[idkey] = id; + } + }); + + return params; + }, + + marketingParams: function() { + return _.extend(_.info.campaignParams(), _.info.clickParams()); + }, + + searchEngine: function(referrer) { + if (referrer.search('https?://(.*)google.([^/?]*)') === 0) { + return 'google'; + } else if (referrer.search('https?://(.*)bing.com') === 0) { + return 'bing'; + } else if (referrer.search('https?://(.*)yahoo.com') === 0) { + return 'yahoo'; + } else if (referrer.search('https?://(.*)duckduckgo.com') === 0) { + return 'duckduckgo'; + } else { + return null; + } + }, + + searchInfo: function(referrer) { + var search = _.info.searchEngine(referrer), + param = (search != 'yahoo') ? 'q' : 'p', + ret = {}; + + if (search !== null) { + ret['$search_engine'] = search; + + var keyword = _.getQueryParam(referrer, param); + if (keyword.length) { + ret['mp_keyword'] = keyword; + } + } + + return ret; + }, + + /** + * This function detects which browser is running this script. + * The order of the checks are important since many user agents + * include key words used in later checks. + */ + browser: function(user_agent, vendor, opera) { + vendor = vendor || ''; // vendor is undefined for at least IE9 + if (opera || _.includes(user_agent, ' OPR/')) { + if (_.includes(user_agent, 'Mini')) { + return 'Opera Mini'; + } + return 'Opera'; + } else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) { + return 'BlackBerry'; + } else if (_.includes(user_agent, 'IEMobile') || _.includes(user_agent, 'WPDesktop')) { + return 'Internet Explorer Mobile'; + } else if (_.includes(user_agent, 'SamsungBrowser/')) { + // https://developer.samsung.com/internet/user-agent-string-format + return 'Samsung Internet'; + } else if (_.includes(user_agent, 'Edge') || _.includes(user_agent, 'Edg/')) { + return 'Microsoft Edge'; + } else if (_.includes(user_agent, 'FBIOS')) { + return 'Facebook Mobile'; + } else if (_.includes(user_agent, 'Chrome')) { + return 'Chrome'; + } else if (_.includes(user_agent, 'CriOS')) { + return 'Chrome iOS'; + } else if (_.includes(user_agent, 'UCWEB') || _.includes(user_agent, 'UCBrowser')) { + return 'UC Browser'; + } else if (_.includes(user_agent, 'FxiOS')) { + return 'Firefox iOS'; + } else if (_.includes(vendor, 'Apple')) { + if (_.includes(user_agent, 'Mobile')) { + return 'Mobile Safari'; + } + return 'Safari'; + } else if (_.includes(user_agent, 'Android')) { + return 'Android Mobile'; + } else if (_.includes(user_agent, 'Konqueror')) { + return 'Konqueror'; + } else if (_.includes(user_agent, 'Firefox')) { + return 'Firefox'; + } else if (_.includes(user_agent, 'MSIE') || _.includes(user_agent, 'Trident/')) { + return 'Internet Explorer'; + } else if (_.includes(user_agent, 'Gecko')) { + return 'Mozilla'; + } else { + return ''; + } + }, + + /** + * This function detects which browser version is running this script, + * parsing major and minor version (e.g., 42.1). User agent strings from: + * http://www.useragentstring.com/pages/useragentstring.php + */ + browserVersion: function(userAgent, vendor, opera) { + var browser = _.info.browser(userAgent, vendor, opera); + var versionRegexs = { + 'Internet Explorer Mobile': /rv:(\d+(\.\d+)?)/, + 'Microsoft Edge': /Edge?\/(\d+(\.\d+)?)/, + 'Chrome': /Chrome\/(\d+(\.\d+)?)/, + 'Chrome iOS': /CriOS\/(\d+(\.\d+)?)/, + 'UC Browser' : /(UCBrowser|UCWEB)\/(\d+(\.\d+)?)/, + 'Safari': /Version\/(\d+(\.\d+)?)/, + 'Mobile Safari': /Version\/(\d+(\.\d+)?)/, + 'Opera': /(Opera|OPR)\/(\d+(\.\d+)?)/, + 'Firefox': /Firefox\/(\d+(\.\d+)?)/, + 'Firefox iOS': /FxiOS\/(\d+(\.\d+)?)/, + 'Konqueror': /Konqueror:(\d+(\.\d+)?)/, + 'BlackBerry': /BlackBerry (\d+(\.\d+)?)/, + 'Android Mobile': /android\s(\d+(\.\d+)?)/, + 'Samsung Internet': /SamsungBrowser\/(\d+(\.\d+)?)/, + 'Internet Explorer': /(rv:|MSIE )(\d+(\.\d+)?)/, + 'Mozilla': /rv:(\d+(\.\d+)?)/ + }; + var regex = versionRegexs[browser]; + if (regex === undefined) { + return null; + } + var matches = userAgent.match(regex); + if (!matches) { + return null; + } + return parseFloat(matches[matches.length - 2]); + }, + + os: function() { + var a = userAgent; + if (/Windows/i.test(a)) { + if (/Phone/.test(a) || /WPDesktop/.test(a)) { + return 'Windows Phone'; + } + return 'Windows'; + } else if (/(iPhone|iPad|iPod)/.test(a)) { + return 'iOS'; + } else if (/Android/.test(a)) { + return 'Android'; + } else if (/(BlackBerry|PlayBook|BB10)/i.test(a)) { + return 'BlackBerry'; + } else if (/Mac/i.test(a)) { + return 'Mac OS X'; + } else if (/Linux/.test(a)) { + return 'Linux'; + } else if (/CrOS/.test(a)) { + return 'Chrome OS'; + } else { + return ''; + } + }, + + device: function(user_agent) { + if (/Windows Phone/i.test(user_agent) || /WPDesktop/.test(user_agent)) { + return 'Windows Phone'; + } else if (/iPad/.test(user_agent)) { + return 'iPad'; + } else if (/iPod/.test(user_agent)) { + return 'iPod Touch'; + } else if (/iPhone/.test(user_agent)) { + return 'iPhone'; + } else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) { + return 'BlackBerry'; + } else if (/Android/.test(user_agent)) { + return 'Android'; + } else { + return ''; + } + }, + + referringDomain: function(referrer) { + var split = referrer.split('/'); + if (split.length >= 3) { + return split[2]; + } + return ''; + }, + + properties: function() { + return _.extend(_.strip_empty_properties({ + '$os': _.info.os(), + '$browser': _.info.browser(userAgent, navigator.vendor, windowOpera), + '$referrer': document$1.referrer, + '$referring_domain': _.info.referringDomain(document$1.referrer), + '$device': _.info.device(userAgent) + }), { + '$current_url': window$1.location.href, + '$browser_version': _.info.browserVersion(userAgent, navigator.vendor, windowOpera), + '$screen_height': screen.height, + '$screen_width': screen.width, + 'mp_lib': 'web', + '$lib_version': Config.LIB_VERSION, + '$insert_id': cheap_guid(), + 'time': _.timestamp() / 1000 // epoch time in seconds + }); + }, + + people_properties: function() { + return _.extend(_.strip_empty_properties({ + '$os': _.info.os(), + '$browser': _.info.browser(userAgent, navigator.vendor, windowOpera) + }), { + '$browser_version': _.info.browserVersion(userAgent, navigator.vendor, windowOpera) + }); + }, + + mpPageViewProperties: function() { + return _.strip_empty_properties({ + 'current_page_title': document$1.title, + 'current_domain': window$1.location.hostname, + 'current_url_path': window$1.location.pathname, + 'current_url_protocol': window$1.location.protocol, + 'current_url_search': window$1.location.search + }); + } +}; + +var cheap_guid = function(maxlen) { + var guid = Math.random().toString(36).substring(2, 10) + Math.random().toString(36).substring(2, 10); + return maxlen ? guid.substring(0, maxlen) : guid; +}; + +// naive way to extract domain name (example.com) from full hostname (my.sub.example.com) +var SIMPLE_DOMAIN_MATCH_REGEX = /[a-z0-9][a-z0-9-]*\.[a-z]+$/i; +// this next one attempts to account for some ccSLDs, e.g. extracting oxford.ac.uk from www.oxford.ac.uk +var DOMAIN_MATCH_REGEX = /[a-z0-9][a-z0-9-]+\.[a-z.]{2,6}$/i; +/** + * Attempts to extract main domain name from full hostname, using a few blunt heuristics. For + * common TLDs like .com/.org that always have a simple SLD.TLD structure (example.com), we + * simply extract the last two .-separated parts of the hostname (SIMPLE_DOMAIN_MATCH_REGEX). + * For others, we attempt to account for short ccSLD+TLD combos (.ac.uk) with the legacy + * DOMAIN_MATCH_REGEX (kept to maintain backwards compatibility with existing Mixpanel + * integrations). The only _reliable_ way to extract domain from hostname is with an up-to-date + * list like at https://publicsuffix.org/ so for cases that this helper fails at, the SDK + * offers the 'cookie_domain' config option to set it explicitly. + * @example + * extract_domain('my.sub.example.com') + * // 'example.com' + */ +var extract_domain = function(hostname) { + var domain_regex = DOMAIN_MATCH_REGEX; + var parts = hostname.split('.'); + var tld = parts[parts.length - 1]; + if (tld.length > 4 || tld === 'com' || tld === 'org') { + domain_regex = SIMPLE_DOMAIN_MATCH_REGEX; + } + var matches = hostname.match(domain_regex); + return matches ? matches[0] : ''; +}; + +var JSONStringify = null; +var JSONParse = null; +if (typeof JSON !== 'undefined') { + JSONStringify = JSON.stringify; + JSONParse = JSON.parse; +} +JSONStringify = JSONStringify || _.JSONEncode; +JSONParse = JSONParse || _.JSONDecode; + +// EXPORTS (for closure compiler) +_['toArray'] = _.toArray; +_['isObject'] = _.isObject; +_['JSONEncode'] = _.JSONEncode; +_['JSONDecode'] = _.JSONDecode; +_['isBlockedUA'] = _.isBlockedUA; +_['isEmptyObject'] = _.isEmptyObject; +_['info'] = _.info; +_['info']['device'] = _.info.device; +_['info']['browser'] = _.info.browser; +_['info']['browserVersion'] = _.info.browserVersion; +_['info']['properties'] = _.info.properties; + +/** + * DomTracker Object + * @constructor + */ +var DomTracker = function() {}; + + +// interface +DomTracker.prototype.create_properties = function() {}; +DomTracker.prototype.event_handler = function() {}; +DomTracker.prototype.after_track_handler = function() {}; + +DomTracker.prototype.init = function(mixpanel_instance) { + this.mp = mixpanel_instance; + return this; +}; + +/** + * @param {Object|string} query + * @param {string} event_name + * @param {Object=} properties + * @param {function=} user_callback + */ +DomTracker.prototype.track = function(query, event_name, properties, user_callback) { + var that = this; + var elements = _.dom_query(query); + + if (elements.length === 0) { + console$1.error('The DOM query (' + query + ') returned 0 elements'); + return; + } + + _.each(elements, function(element) { + _.register_event(element, this.override_event, function(e) { + var options = {}; + var props = that.create_properties(properties, this); + var timeout = that.mp.get_config('track_links_timeout'); + + that.event_handler(e, this, options); + + // in case the mixpanel servers don't get back to us in time + window.setTimeout(that.track_callback(user_callback, props, options, true), timeout); + + // fire the tracking event + that.mp.track(event_name, props, that.track_callback(user_callback, props, options)); + }); + }, this); + + return true; +}; + +/** + * @param {function} user_callback + * @param {Object} props + * @param {boolean=} timeout_occured + */ +DomTracker.prototype.track_callback = function(user_callback, props, options, timeout_occured) { + timeout_occured = timeout_occured || false; + var that = this; + + return function() { + // options is referenced from both callbacks, so we can have + // a 'lock' of sorts to ensure only one fires + if (options.callback_fired) { return; } + options.callback_fired = true; + + if (user_callback && user_callback(timeout_occured, props) === false) { + // user can prevent the default functionality by + // returning false from their callback + return; + } + + that.after_track_handler(props, options, timeout_occured); + }; +}; + +DomTracker.prototype.create_properties = function(properties, element) { + var props; + + if (typeof(properties) === 'function') { + props = properties(element); + } else { + props = _.extend({}, properties); + } + + return props; +}; + +/** + * LinkTracker Object + * @constructor + * @extends DomTracker + */ +var LinkTracker = function() { + this.override_event = 'click'; +}; +_.inherit(LinkTracker, DomTracker); + +LinkTracker.prototype.create_properties = function(properties, element) { + var props = LinkTracker.superclass.create_properties.apply(this, arguments); + + if (element.href) { props['url'] = element.href; } + + return props; +}; + +LinkTracker.prototype.event_handler = function(evt, element, options) { + options.new_tab = ( + evt.which === 2 || + evt.metaKey || + evt.ctrlKey || + element.target === '_blank' + ); + options.href = element.href; + + if (!options.new_tab) { + evt.preventDefault(); + } +}; + +LinkTracker.prototype.after_track_handler = function(props, options) { + if (options.new_tab) { return; } + + setTimeout(function() { + window.location = options.href; + }, 0); +}; + +/** + * FormTracker Object + * @constructor + * @extends DomTracker + */ +var FormTracker = function() { + this.override_event = 'submit'; +}; +_.inherit(FormTracker, DomTracker); + +FormTracker.prototype.event_handler = function(evt, element, options) { + options.element = element; + evt.preventDefault(); +}; + +FormTracker.prototype.after_track_handler = function(props, options) { + setTimeout(function() { + options.element.submit(); + }, 0); +}; + +// eslint-disable-line camelcase + +var logger$2 = console_with_prefix('lock'); + +/** + * SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser + * window/tab at a time will be able to access shared resources. + * + * Based on the Alur and Taubenfeld fast lock + * (http://www.cs.rochester.edu/research/synchronization/pseudocode/fastlock.html) + * with an added timeout to ensure there will be eventual progress in the event + * that a window is closed in the middle of the callback. + * + * Implementation based on the original version by David Wolever (https://github.com/wolever) + * at https://gist.github.com/wolever/5fd7573d1ef6166e8f8c4af286a69432. + * + * @example + * const myLock = new SharedLock('some-key'); + * myLock.withLock(function() { + * console.log('I hold the mutex!'); + * }); + * + * @constructor + */ +var SharedLock = function(key, options) { + options = options || {}; + + this.storageKey = key; + this.storage = options.storage || window.localStorage; + this.pollIntervalMS = options.pollIntervalMS || 100; + this.timeoutMS = options.timeoutMS || 2000; +}; + +// pass in a specific pid to test contention scenarios; otherwise +// it is chosen randomly for each acquisition attempt +SharedLock.prototype.withLock = function(lockedCB, errorCB, pid) { + if (!pid && typeof errorCB !== 'function') { + pid = errorCB; + errorCB = null; + } + + var i = pid || (new Date().getTime() + '|' + Math.random()); + var startTime = new Date().getTime(); + + var key = this.storageKey; + var pollIntervalMS = this.pollIntervalMS; + var timeoutMS = this.timeoutMS; + var storage = this.storage; + + var keyX = key + ':X'; + var keyY = key + ':Y'; + var keyZ = key + ':Z'; + + var reportError = function(err) { + errorCB && errorCB(err); + }; + + var delay = function(cb) { + if (new Date().getTime() - startTime > timeoutMS) { + logger$2.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']'); + storage.removeItem(keyZ); + storage.removeItem(keyY); + loop(); + return; + } + setTimeout(function() { + try { + cb(); + } catch(err) { + reportError(err); + } + }, pollIntervalMS * (Math.random() + 0.1)); + }; + + var waitFor = function(predicate, cb) { + if (predicate()) { + cb(); + } else { + delay(function() { + waitFor(predicate, cb); + }); + } + }; + + var getSetY = function() { + var valY = storage.getItem(keyY); + if (valY && valY !== i) { // if Y == i then this process already has the lock (useful for test cases) + return false; + } else { + storage.setItem(keyY, i); + if (storage.getItem(keyY) === i) { + return true; + } else { + if (!localStorageSupported(storage, true)) { + throw new Error('localStorage support dropped while acquiring lock'); + } + return false; + } + } + }; + + var loop = function() { + storage.setItem(keyX, i); + + waitFor(getSetY, function() { + if (storage.getItem(keyX) === i) { + criticalSection(); + return; + } + + delay(function() { + if (storage.getItem(keyY) !== i) { + loop(); + return; + } + waitFor(function() { + return !storage.getItem(keyZ); + }, criticalSection); + }); + }); + }; + + var criticalSection = function() { + storage.setItem(keyZ, '1'); + try { + lockedCB(); + } finally { + storage.removeItem(keyZ); + if (storage.getItem(keyY) === i) { + storage.removeItem(keyY); + } + if (storage.getItem(keyX) === i) { + storage.removeItem(keyX); + } + } + }; + + try { + if (localStorageSupported(storage, true)) { + loop(); + } else { + throw new Error('localStorage support check failed'); + } + } catch(err) { + reportError(err); + } +}; + +// eslint-disable-line camelcase + +var logger$1 = console_with_prefix('batch'); + +/** + * RequestQueue: queue for batching API requests with localStorage backup for retries. + * Maintains an in-memory queue which represents the source of truth for the current + * page, but also writes all items out to a copy in the browser's localStorage, which + * can be read on subsequent pageloads and retried. For batchability, all the request + * items in the queue should be of the same type (events, people updates, group updates) + * so they can be sent in a single request to the same API endpoint. + * + * LocalStorage keying and locking: In order for reloads and subsequent pageloads of + * the same site to access the same persisted data, they must share the same localStorage + * key (for instance based on project token and queue type). Therefore access to the + * localStorage entry is guarded by an asynchronous mutex (SharedLock) to prevent + * simultaneously open windows/tabs from overwriting each other's data (which would lead + * to data loss in some situations). + * @constructor + */ +var RequestQueue = function(storageKey, options) { + options = options || {}; + this.storageKey = storageKey; + this.storage = options.storage || window.localStorage; + this.reportError = options.errorReporter || _.bind(logger$1.error, logger$1); + this.lock = new SharedLock(storageKey, {storage: this.storage}); + + this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios + + this.memQueue = []; +}; + +/** + * Add one item to queues (memory and localStorage). The queued entry includes + * the given item along with an auto-generated ID and a "flush-after" timestamp. + * It is expected that the item will be sent over the network and dequeued + * before the flush-after time; if this doesn't happen it is considered orphaned + * (e.g., the original tab where it was enqueued got closed before it could be + * sent) and the item can be sent by any tab that finds it in localStorage. + * + * The final callback param is called with a param indicating success or + * failure of the enqueue operation; it is asynchronous because the localStorage + * lock is asynchronous. + */ +RequestQueue.prototype.enqueue = function(item, flushInterval, cb) { + var queueEntry = { + 'id': cheap_guid(), + 'flushAfter': new Date().getTime() + flushInterval * 2, + 'payload': item + }; + + this.lock.withLock(_.bind(function lockAcquired() { + var succeeded; + try { + var storedQueue = this.readFromStorage(); + storedQueue.push(queueEntry); + succeeded = this.saveToStorage(storedQueue); + if (succeeded) { + // only add to in-memory queue when storage succeeds + this.memQueue.push(queueEntry); + } + } catch(err) { + this.reportError('Error enqueueing item', item); + succeeded = false; + } + if (cb) { + cb(succeeded); + } + }, this), _.bind(function lockFailure(err) { + this.reportError('Error acquiring storage lock', err); + if (cb) { + cb(false); + } + }, this), this.pid); +}; + +/** + * Read out the given number of queue entries. If this.memQueue + * has fewer than batchSize items, then look for "orphaned" items + * in the persisted queue (items where the 'flushAfter' time has + * already passed). + */ +RequestQueue.prototype.fillBatch = function(batchSize) { + var batch = this.memQueue.slice(0, batchSize); + if (batch.length < batchSize) { + // don't need lock just to read events; localStorage is thread-safe + // and the worst that could happen is a duplicate send of some + // orphaned events, which will be deduplicated on the server side + var storedQueue = this.readFromStorage(); + if (storedQueue.length) { + // item IDs already in batch; don't duplicate out of storage + var idsInBatch = {}; // poor man's Set + _.each(batch, function(item) { idsInBatch[item['id']] = true; }); + + for (var i = 0; i < storedQueue.length; i++) { + var item = storedQueue[i]; + if (new Date().getTime() > item['flushAfter'] && !idsInBatch[item['id']]) { + item.orphaned = true; + batch.push(item); + if (batch.length >= batchSize) { + break; + } + } + } + } + } + return batch; +}; + +/** + * Remove items with matching 'id' from array (immutably) + * also remove any item without a valid id (e.g., malformed + * storage entries). + */ +var filterOutIDsAndInvalid = function(items, idSet) { + var filteredItems = []; + _.each(items, function(item) { + if (item['id'] && !idSet[item['id']]) { + filteredItems.push(item); + } + }); + return filteredItems; +}; + +/** + * Remove items with matching IDs from both in-memory queue + * and persisted queue + */ +RequestQueue.prototype.removeItemsByID = function(ids, cb) { + var idSet = {}; // poor man's Set + _.each(ids, function(id) { idSet[id] = true; }); + + this.memQueue = filterOutIDsAndInvalid(this.memQueue, idSet); + + var removeFromStorage = _.bind(function() { + var succeeded; + try { + var storedQueue = this.readFromStorage(); + storedQueue = filterOutIDsAndInvalid(storedQueue, idSet); + succeeded = this.saveToStorage(storedQueue); + + // an extra check: did storage report success but somehow + // the items are still there? + if (succeeded) { + storedQueue = this.readFromStorage(); + for (var i = 0; i < storedQueue.length; i++) { + var item = storedQueue[i]; + if (item['id'] && !!idSet[item['id']]) { + this.reportError('Item not removed from storage'); + return false; + } + } + } + } catch(err) { + this.reportError('Error removing items', ids); + succeeded = false; + } + return succeeded; + }, this); + + this.lock.withLock(function lockAcquired() { + var succeeded = removeFromStorage(); + if (cb) { + cb(succeeded); + } + }, _.bind(function lockFailure(err) { + var succeeded = false; + this.reportError('Error acquiring storage lock', err); + if (!localStorageSupported(this.storage, true)) { + // Looks like localStorage writes have stopped working sometime after + // initialization (probably full), and so nobody can acquire locks + // anymore. Consider it temporarily safe to remove items without the + // lock, since nobody's writing successfully anyway. + succeeded = removeFromStorage(); + if (!succeeded) { + // OK, we couldn't even write out the smaller queue. Try clearing it + // entirely. + try { + this.storage.removeItem(this.storageKey); + } catch(err) { + this.reportError('Error clearing queue', err); + } + } + } + if (cb) { + cb(succeeded); + } + }, this), this.pid); +}; + +// internal helper for RequestQueue.updatePayloads +var updatePayloads = function(existingItems, itemsToUpdate) { + var newItems = []; + _.each(existingItems, function(item) { + var id = item['id']; + if (id in itemsToUpdate) { + var newPayload = itemsToUpdate[id]; + if (newPayload !== null) { + item['payload'] = newPayload; + newItems.push(item); + } + } else { + // no update + newItems.push(item); + } + }); + return newItems; +}; + +/** + * Update payloads of given items in both in-memory queue and + * persisted queue. Items set to null are removed from queues. + */ +RequestQueue.prototype.updatePayloads = function(itemsToUpdate, cb) { + this.memQueue = updatePayloads(this.memQueue, itemsToUpdate); + this.lock.withLock(_.bind(function lockAcquired() { + var succeeded; + try { + var storedQueue = this.readFromStorage(); + storedQueue = updatePayloads(storedQueue, itemsToUpdate); + succeeded = this.saveToStorage(storedQueue); + } catch(err) { + this.reportError('Error updating items', itemsToUpdate); + succeeded = false; + } + if (cb) { + cb(succeeded); + } + }, this), _.bind(function lockFailure(err) { + this.reportError('Error acquiring storage lock', err); + if (cb) { + cb(false); + } + }, this), this.pid); +}; + +/** + * Read and parse items array from localStorage entry, handling + * malformed/missing data if necessary. + */ +RequestQueue.prototype.readFromStorage = function() { + var storageEntry; + try { + storageEntry = this.storage.getItem(this.storageKey); + if (storageEntry) { + storageEntry = JSONParse(storageEntry); + if (!_.isArray(storageEntry)) { + this.reportError('Invalid storage entry:', storageEntry); + storageEntry = null; + } + } + } catch (err) { + this.reportError('Error retrieving queue', err); + storageEntry = null; + } + return storageEntry || []; +}; + +/** + * Serialize the given items array to localStorage. + */ +RequestQueue.prototype.saveToStorage = function(queue) { + try { + this.storage.setItem(this.storageKey, JSONStringify(queue)); + return true; + } catch (err) { + this.reportError('Error saving queue', err); + return false; + } +}; + +/** + * Clear out queues (memory and localStorage). + */ +RequestQueue.prototype.clear = function() { + this.memQueue = []; + this.storage.removeItem(this.storageKey); +}; + +// eslint-disable-line camelcase + +// maximum interval between request retries after exponential backoff +var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes + +var logger = console_with_prefix('batch'); + +/** + * RequestBatcher: manages the queueing, flushing, retry etc of requests of one + * type (events, people, groups). + * Uses RequestQueue to manage the backing store. + * @constructor + */ +var RequestBatcher = function(storageKey, options) { + this.errorReporter = options.errorReporter; + this.queue = new RequestQueue(storageKey, { + errorReporter: _.bind(this.reportError, this), + storage: options.storage + }); + + this.libConfig = options.libConfig; + this.sendRequest = options.sendRequestFunc; + this.beforeSendHook = options.beforeSendHook; + this.stopAllBatching = options.stopAllBatchingFunc; + + // seed variable batch size + flush interval with configured values + this.batchSize = this.libConfig['batch_size']; + this.flushInterval = this.libConfig['batch_flush_interval_ms']; + + this.stopped = !this.libConfig['batch_autostart']; + this.consecutiveRemovalFailures = 0; + + // extra client-side dedupe + this.itemIdsSentSuccessfully = {}; +}; + +/** + * Add one item to queue. + */ +RequestBatcher.prototype.enqueue = function(item, cb) { + this.queue.enqueue(item, this.flushInterval, cb); +}; + +/** + * Start flushing batches at the configured time interval. Must call + * this method upon SDK init in order to send anything over the network. + */ +RequestBatcher.prototype.start = function() { + this.stopped = false; + this.consecutiveRemovalFailures = 0; + this.flush(); +}; + +/** + * Stop flushing batches. Can be restarted by calling start(). + */ +RequestBatcher.prototype.stop = function() { + this.stopped = true; + if (this.timeoutID) { + clearTimeout(this.timeoutID); + this.timeoutID = null; + } +}; + +/** + * Clear out queue. + */ +RequestBatcher.prototype.clear = function() { + this.queue.clear(); +}; + +/** + * Restore batch size configuration to whatever is set in the main SDK. + */ +RequestBatcher.prototype.resetBatchSize = function() { + this.batchSize = this.libConfig['batch_size']; +}; + +/** + * Restore flush interval time configuration to whatever is set in the main SDK. + */ +RequestBatcher.prototype.resetFlush = function() { + this.scheduleFlush(this.libConfig['batch_flush_interval_ms']); +}; + +/** + * Schedule the next flush in the given number of milliseconds. + */ +RequestBatcher.prototype.scheduleFlush = function(flushMS) { + this.flushInterval = flushMS; + if (!this.stopped) { // don't schedule anymore if batching has been stopped + this.timeoutID = setTimeout(_.bind(this.flush, this), this.flushInterval); + } +}; + +/** + * Flush one batch to network. Depending on success/failure modes, it will either + * remove the batch from the queue or leave it in for retry, and schedule the next + * flush. In cases of most network or API failures, it will back off exponentially + * when retrying. + * @param {Object} [options] + * @param {boolean} [options.sendBeacon] - whether to send batch with + * navigator.sendBeacon (only useful for sending batches before page unloads, as + * sendBeacon offers no callbacks or status indications) + */ +RequestBatcher.prototype.flush = function(options) { + try { + + if (this.requestInProgress) { + logger.log('Flush: Request already in progress'); + return; + } + + options = options || {}; + var timeoutMS = this.libConfig['batch_request_timeout_ms']; + var startTime = new Date().getTime(); + var currentBatchSize = this.batchSize; + var batch = this.queue.fillBatch(currentBatchSize); + var dataForRequest = []; + var transformedItems = {}; + _.each(batch, function(item) { + var payload = item['payload']; + if (this.beforeSendHook && !item.orphaned) { + payload = this.beforeSendHook(payload); + } + if (payload) { + // mp_sent_by_lib_version prop captures which lib version actually + // sends each event (regardless of which version originally queued + // it for sending) + if (payload['event'] && payload['properties']) { + payload['properties'] = _.extend( + {}, + payload['properties'], + {'mp_sent_by_lib_version': Config.LIB_VERSION} + ); + } + var addPayload = true; + var itemId = item['id']; + if (itemId) { + if ((this.itemIdsSentSuccessfully[itemId] || 0) > 5) { + this.reportError('[dupe] item ID sent too many times, not sending', { + item: item, + batchSize: batch.length, + timesSent: this.itemIdsSentSuccessfully[itemId] + }); + addPayload = false; + } + } else { + this.reportError('[dupe] found item with no ID', {item: item}); + } + + if (addPayload) { + dataForRequest.push(payload); + } + } + transformedItems[item['id']] = payload; + }, this); + if (dataForRequest.length < 1) { + this.resetFlush(); + return; // nothing to do + } + + this.requestInProgress = true; + + var batchSendCallback = _.bind(function(res) { + this.requestInProgress = false; + + try { + + // handle API response in a try-catch to make sure we can reset the + // flush operation if something goes wrong + + var removeItemsFromQueue = false; + if (options.unloading) { + // update persisted data to include hook transformations + this.queue.updatePayloads(transformedItems); + } else if ( + _.isObject(res) && + res.error === 'timeout' && + new Date().getTime() - startTime >= timeoutMS + ) { + this.reportError('Network timeout; retrying'); + this.flush(); + } else if ( + _.isObject(res) && + res.xhr_req && + (res.xhr_req['status'] >= 500 || res.xhr_req['status'] === 429 || res.error === 'timeout') + ) { + // network or API error, or 429 Too Many Requests, retry + var retryMS = this.flushInterval * 2; + var headers = res.xhr_req['responseHeaders']; + if (headers) { + var retryAfter = headers['Retry-After']; + if (retryAfter) { + retryMS = (parseInt(retryAfter, 10) * 1000) || retryMS; + } + } + retryMS = Math.min(MAX_RETRY_INTERVAL_MS, retryMS); + this.reportError('Error; retry in ' + retryMS + ' ms'); + this.scheduleFlush(retryMS); + } else if (_.isObject(res) && res.xhr_req && res.xhr_req['status'] === 413) { + // 413 Payload Too Large + if (batch.length > 1) { + var halvedBatchSize = Math.max(1, Math.floor(currentBatchSize / 2)); + this.batchSize = Math.min(this.batchSize, halvedBatchSize, batch.length - 1); + this.reportError('413 response; reducing batch size to ' + this.batchSize); + this.resetFlush(); + } else { + this.reportError('Single-event request too large; dropping', batch); + this.resetBatchSize(); + removeItemsFromQueue = true; + } + } else { + // successful network request+response; remove each item in batch from queue + // (even if it was e.g. a 400, in which case retrying won't help) + removeItemsFromQueue = true; + } + + if (removeItemsFromQueue) { + this.queue.removeItemsByID( + _.map(batch, function(item) { return item['id']; }), + _.bind(function(succeeded) { + if (succeeded) { + this.consecutiveRemovalFailures = 0; + this.flush(); // handle next batch if the queue isn't empty + } else { + this.reportError('Failed to remove items from queue'); + if (++this.consecutiveRemovalFailures > 5) { + this.reportError('Too many queue failures; disabling batching system.'); + this.stopAllBatching(); + } else { + this.resetFlush(); + } + } + }, this) + ); + + // client-side dedupe + _.each(batch, _.bind(function(item) { + var itemId = item['id']; + if (itemId) { + this.itemIdsSentSuccessfully[itemId] = this.itemIdsSentSuccessfully[itemId] || 0; + this.itemIdsSentSuccessfully[itemId]++; + if (this.itemIdsSentSuccessfully[itemId] > 5) { + this.reportError('[dupe] item ID sent too many times', { + item: item, + batchSize: batch.length, + timesSent: this.itemIdsSentSuccessfully[itemId] + }); + } + } else { + this.reportError('[dupe] found item with no ID while removing', {item: item}); + } + }, this)); + } + + } catch(err) { + this.reportError('Error handling API response', err); + this.resetFlush(); + } + }, this); + var requestOptions = { + method: 'POST', + verbose: true, + ignore_json_errors: true, // eslint-disable-line camelcase + timeout_ms: timeoutMS // eslint-disable-line camelcase + }; + if (options.unloading) { + requestOptions.transport = 'sendBeacon'; + } + logger.log('MIXPANEL REQUEST:', dataForRequest); + this.sendRequest(dataForRequest, requestOptions, batchSendCallback); + + } catch(err) { + this.reportError('Error flushing request queue', err); + this.resetFlush(); + } +}; + +/** + * Log error to global logger and optional user-defined logger. + */ +RequestBatcher.prototype.reportError = function(msg, err) { + logger.error.apply(logger.error, arguments); + if (this.errorReporter) { + try { + if (!(err instanceof Error)) { + err = new Error(msg); + } + this.errorReporter(msg, err); + } catch(err) { + logger.error(err); + } + } +}; + +/** + * A function used to track a Mixpanel event (e.g. MixpanelLib.track) + * @callback trackFunction + * @param {String} event_name The name of the event. This can be anything the user does - 'Button Click', 'Sign Up', 'Item Purchased', etc. + * @param {Object} [properties] A set of properties to include with the event you're sending. These describe the user who did the event or details about the event itself. + * @param {Function} [callback] If provided, the callback function will be called after tracking the event. + */ + +/** Public **/ + +var GDPR_DEFAULT_PERSISTENCE_PREFIX = '__mp_opt_in_out_'; + +/** + * Opt the user in to data tracking and cookies/localstorage for the given token + * @param {string} token - Mixpanel project tracking token + * @param {Object} [options] + * @param {trackFunction} [options.track] - function used for tracking a Mixpanel event to record the opt-in action + * @param {string} [options.trackEventName] - event name to be used for tracking the opt-in action + * @param {Object} [options.trackProperties] - set of properties to be tracked along with the opt-in action + * @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage + * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookieExpiration] - number of days until the opt-in cookie expires + * @param {string} [options.cookieDomain] - custom cookie domain + * @param {boolean} [options.crossSiteCookie] - whether the opt-in cookie is set as cross-site-enabled + * @param {boolean} [options.crossSubdomainCookie] - whether the opt-in cookie is set as cross-subdomain or not + * @param {boolean} [options.secureCookie] - whether the opt-in cookie is set as secure or not + */ +function optIn(token, options) { + _optInOut(true, token, options); +} + +/** + * Opt the user out of data tracking and cookies/localstorage for the given token + * @param {string} token - Mixpanel project tracking token + * @param {Object} [options] + * @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage + * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookieExpiration] - number of days until the opt-out cookie expires + * @param {string} [options.cookieDomain] - custom cookie domain + * @param {boolean} [options.crossSiteCookie] - whether the opt-in cookie is set as cross-site-enabled + * @param {boolean} [options.crossSubdomainCookie] - whether the opt-out cookie is set as cross-subdomain or not + * @param {boolean} [options.secureCookie] - whether the opt-out cookie is set as secure or not + */ +function optOut(token, options) { + _optInOut(false, token, options); +} + +/** + * Check whether the user has opted in to data tracking and cookies/localstorage for the given token + * @param {string} token - Mixpanel project tracking token + * @param {Object} [options] + * @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage + * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name + * @returns {boolean} whether the user has opted in to the given opt type + */ +function hasOptedIn(token, options) { + return _getStorageValue(token, options) === '1'; +} + +/** + * Check whether the user has opted out of data tracking and cookies/localstorage for the given token + * @param {string} token - Mixpanel project tracking token + * @param {Object} [options] + * @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage + * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name + * @param {boolean} [options.ignoreDnt] - flag to ignore browser DNT settings and always return false + * @returns {boolean} whether the user has opted out of the given opt type + */ +function hasOptedOut(token, options) { + if (_hasDoNotTrackFlagOn(options)) { + console$1.warn('This browser has "Do Not Track" enabled. This will prevent the Mixpanel SDK from sending any data. To ignore the "Do Not Track" browser setting, initialize the Mixpanel instance with the config "ignore_dnt: true"'); + return true; + } + var optedOut = _getStorageValue(token, options) === '0'; + if (optedOut) { + console$1.warn('You are opted out of Mixpanel tracking. This will prevent the Mixpanel SDK from sending any data.'); + } + return optedOut; +} + +/** + * Wrap a MixpanelLib method with a check for whether the user is opted out of data tracking and cookies/localstorage for the given token + * If the user has opted out, return early instead of executing the method. + * If a callback argument was provided, execute it passing the 0 error code. + * @param {function} method - wrapped method to be executed if the user has not opted out + * @returns {*} the result of executing method OR undefined if the user has opted out + */ +function addOptOutCheckMixpanelLib(method) { + return _addOptOutCheck(method, function(name) { + return this.get_config(name); + }); +} + +/** + * Wrap a MixpanelPeople method with a check for whether the user is opted out of data tracking and cookies/localstorage for the given token + * If the user has opted out, return early instead of executing the method. + * If a callback argument was provided, execute it passing the 0 error code. + * @param {function} method - wrapped method to be executed if the user has not opted out + * @returns {*} the result of executing method OR undefined if the user has opted out + */ +function addOptOutCheckMixpanelPeople(method) { + return _addOptOutCheck(method, function(name) { + return this._get_config(name); + }); +} + +/** + * Wrap a MixpanelGroup method with a check for whether the user is opted out of data tracking and cookies/localstorage for the given token + * If the user has opted out, return early instead of executing the method. + * If a callback argument was provided, execute it passing the 0 error code. + * @param {function} method - wrapped method to be executed if the user has not opted out + * @returns {*} the result of executing method OR undefined if the user has opted out + */ +function addOptOutCheckMixpanelGroup(method) { + return _addOptOutCheck(method, function(name) { + return this._get_config(name); + }); +} + +/** + * Clear the user's opt in/out status of data tracking and cookies/localstorage for the given token + * @param {string} token - Mixpanel project tracking token + * @param {Object} [options] + * @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage + * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookieExpiration] - number of days until the opt-in cookie expires + * @param {string} [options.cookieDomain] - custom cookie domain + * @param {boolean} [options.crossSiteCookie] - whether the opt-in cookie is set as cross-site-enabled + * @param {boolean} [options.crossSubdomainCookie] - whether the opt-in cookie is set as cross-subdomain or not + * @param {boolean} [options.secureCookie] - whether the opt-in cookie is set as secure or not + */ +function clearOptInOut(token, options) { + options = options || {}; + _getStorage(options).remove( + _getStorageKey(token, options), !!options.crossSubdomainCookie, options.cookieDomain + ); +} + +/** Private **/ + +/** + * Get storage util + * @param {Object} [options] + * @param {string} [options.persistenceType] + * @returns {object} either _.cookie or _.localstorage + */ +function _getStorage(options) { + options = options || {}; + return options.persistenceType === 'localStorage' ? _.localStorage : _.cookie; +} + +/** + * Get the name of the cookie that is used for the given opt type (tracking, cookie, etc.) + * @param {string} token - Mixpanel project tracking token + * @param {Object} [options] + * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name + * @returns {string} the name of the cookie for the given opt type + */ +function _getStorageKey(token, options) { + options = options || {}; + return (options.persistencePrefix || GDPR_DEFAULT_PERSISTENCE_PREFIX) + token; +} + +/** + * Get the value of the cookie that is used for the given opt type (tracking, cookie, etc.) + * @param {string} token - Mixpanel project tracking token + * @param {Object} [options] + * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name + * @returns {string} the value of the cookie for the given opt type + */ +function _getStorageValue(token, options) { + return _getStorage(options).get(_getStorageKey(token, options)); +} + +/** + * Check whether the user has set the DNT/doNotTrack setting to true in their browser + * @param {Object} [options] + * @param {string} [options.window] - alternate window object to check; used to force various DNT settings in browser tests + * @param {boolean} [options.ignoreDnt] - flag to ignore browser DNT settings and always return false + * @returns {boolean} whether the DNT setting is true + */ +function _hasDoNotTrackFlagOn(options) { + if (options && options.ignoreDnt) { + return false; + } + var win = (options && options.window) || window$1; + var nav = win['navigator'] || {}; + var hasDntOn = false; + + _.each([ + nav['doNotTrack'], // standard + nav['msDoNotTrack'], + win['doNotTrack'] + ], function(dntValue) { + if (_.includes([true, 1, '1', 'yes'], dntValue)) { + hasDntOn = true; + } + }); + + return hasDntOn; +} + +/** + * Set cookie/localstorage for the user indicating that they are opted in or out for the given opt type + * @param {boolean} optValue - whether to opt the user in or out for the given opt type + * @param {string} token - Mixpanel project tracking token + * @param {Object} [options] + * @param {trackFunction} [options.track] - function used for tracking a Mixpanel event to record the opt-in action + * @param {string} [options.trackEventName] - event name to be used for tracking the opt-in action + * @param {Object} [options.trackProperties] - set of properties to be tracked along with the opt-in action + * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookieExpiration] - number of days until the opt-in cookie expires + * @param {string} [options.cookieDomain] - custom cookie domain + * @param {boolean} [options.crossSiteCookie] - whether the opt-in cookie is set as cross-site-enabled + * @param {boolean} [options.crossSubdomainCookie] - whether the opt-in cookie is set as cross-subdomain or not + * @param {boolean} [options.secureCookie] - whether the opt-in cookie is set as secure or not + */ +function _optInOut(optValue, token, options) { + if (!_.isString(token) || !token.length) { + console$1.error('gdpr.' + (optValue ? 'optIn' : 'optOut') + ' called with an invalid token'); + return; + } + + options = options || {}; + + _getStorage(options).set( + _getStorageKey(token, options), + optValue ? 1 : 0, + _.isNumber(options.cookieExpiration) ? options.cookieExpiration : null, + !!options.crossSubdomainCookie, + !!options.secureCookie, + !!options.crossSiteCookie, + options.cookieDomain + ); + + if (options.track && optValue) { // only track event if opting in (optValue=true) + options.track(options.trackEventName || '$opt_in', options.trackProperties, { + 'send_immediately': true + }); + } +} + +/** + * Wrap a method with a check for whether the user is opted out of data tracking and cookies/localstorage for the given token + * If the user has opted out, return early instead of executing the method. + * If a callback argument was provided, execute it passing the 0 error code. + * @param {function} method - wrapped method to be executed if the user has not opted out + * @param {function} getConfigValue - getter function for the Mixpanel API token and other options to be used with opt-out check + * @returns {*} the result of executing method OR undefined if the user has opted out + */ +function _addOptOutCheck(method, getConfigValue) { + return function() { + var optedOut = false; + + try { + var token = getConfigValue.call(this, 'token'); + var ignoreDnt = getConfigValue.call(this, 'ignore_dnt'); + var persistenceType = getConfigValue.call(this, 'opt_out_tracking_persistence_type'); + var persistencePrefix = getConfigValue.call(this, 'opt_out_tracking_cookie_prefix'); + var win = getConfigValue.call(this, 'window'); // used to override window during browser tests + + if (token) { // if there was an issue getting the token, continue method execution as normal + optedOut = hasOptedOut(token, { + ignoreDnt: ignoreDnt, + persistenceType: persistenceType, + persistencePrefix: persistencePrefix, + window: win + }); + } + } catch(err) { + console$1.error('Unexpected error when checking tracking opt-out status: ' + err); + } + + if (!optedOut) { + return method.apply(this, arguments); + } + + var callback = arguments[arguments.length - 1]; + if (typeof(callback) === 'function') { + callback(0); + } + + return; + }; +} + +/** @const */ var SET_ACTION = '$set'; +/** @const */ var SET_ONCE_ACTION = '$set_once'; +/** @const */ var UNSET_ACTION = '$unset'; +/** @const */ var ADD_ACTION = '$add'; +/** @const */ var APPEND_ACTION = '$append'; +/** @const */ var UNION_ACTION = '$union'; +/** @const */ var REMOVE_ACTION = '$remove'; +/** @const */ var DELETE_ACTION = '$delete'; + +// Common internal methods for mixpanel.people and mixpanel.group APIs. +// These methods shouldn't involve network I/O. +var apiActions = { + set_action: function(prop, to) { + var data = {}; + var $set = {}; + if (_.isObject(prop)) { + _.each(prop, function(v, k) { + if (!this._is_reserved_property(k)) { + $set[k] = v; + } + }, this); + } else { + $set[prop] = to; + } + + data[SET_ACTION] = $set; + return data; + }, + + unset_action: function(prop) { + var data = {}; + var $unset = []; + if (!_.isArray(prop)) { + prop = [prop]; + } + + _.each(prop, function(k) { + if (!this._is_reserved_property(k)) { + $unset.push(k); + } + }, this); + + data[UNSET_ACTION] = $unset; + return data; + }, + + set_once_action: function(prop, to) { + var data = {}; + var $set_once = {}; + if (_.isObject(prop)) { + _.each(prop, function(v, k) { + if (!this._is_reserved_property(k)) { + $set_once[k] = v; + } + }, this); + } else { + $set_once[prop] = to; + } + data[SET_ONCE_ACTION] = $set_once; + return data; + }, + + union_action: function(list_name, values) { + var data = {}; + var $union = {}; + if (_.isObject(list_name)) { + _.each(list_name, function(v, k) { + if (!this._is_reserved_property(k)) { + $union[k] = _.isArray(v) ? v : [v]; + } + }, this); + } else { + $union[list_name] = _.isArray(values) ? values : [values]; + } + data[UNION_ACTION] = $union; + return data; + }, + + append_action: function(list_name, value) { + var data = {}; + var $append = {}; + if (_.isObject(list_name)) { + _.each(list_name, function(v, k) { + if (!this._is_reserved_property(k)) { + $append[k] = v; + } + }, this); + } else { + $append[list_name] = value; + } + data[APPEND_ACTION] = $append; + return data; + }, + + remove_action: function(list_name, value) { + var data = {}; + var $remove = {}; + if (_.isObject(list_name)) { + _.each(list_name, function(v, k) { + if (!this._is_reserved_property(k)) { + $remove[k] = v; + } + }, this); + } else { + $remove[list_name] = value; + } + data[REMOVE_ACTION] = $remove; + return data; + }, + + delete_action: function() { + var data = {}; + data[DELETE_ACTION] = ''; + return data; + } +}; + +/** + * Mixpanel Group Object + * @constructor + */ +var MixpanelGroup = function() {}; + +_.extend(MixpanelGroup.prototype, apiActions); + +MixpanelGroup.prototype._init = function(mixpanel_instance, group_key, group_id) { + this._mixpanel = mixpanel_instance; + this._group_key = group_key; + this._group_id = group_id; +}; + +/** + * Set properties on a group. + * + * ### Usage: + * + * mixpanel.get_group('company', 'mixpanel').set('Location', '405 Howard'); + * + * // or set multiple properties at once + * mixpanel.get_group('company', 'mixpanel').set({ + * 'Location': '405 Howard', + * 'Founded' : 2009, + * }); + * // properties can be strings, integers, dates, or lists + * + * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values. + * @param {*} [to] A value to set on the given property name + * @param {Function} [callback] If provided, the callback will be called after the tracking event + */ +MixpanelGroup.prototype.set = addOptOutCheckMixpanelGroup(function(prop, to, callback) { + var data = this.set_action(prop, to); + if (_.isObject(prop)) { + callback = to; + } + return this._send_request(data, callback); +}); + +/** + * Set properties on a group, only if they do not yet exist. + * This will not overwrite previous group property values, unlike + * group.set(). + * + * ### Usage: + * + * mixpanel.get_group('company', 'mixpanel').set_once('Location', '405 Howard'); + * + * // or set multiple properties at once + * mixpanel.get_group('company', 'mixpanel').set_once({ + * 'Location': '405 Howard', + * 'Founded' : 2009, + * }); + * // properties can be strings, integers, lists or dates + * + * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values. + * @param {*} [to] A value to set on the given property name + * @param {Function} [callback] If provided, the callback will be called after the tracking event + */ +MixpanelGroup.prototype.set_once = addOptOutCheckMixpanelGroup(function(prop, to, callback) { + var data = this.set_once_action(prop, to); + if (_.isObject(prop)) { + callback = to; + } + return this._send_request(data, callback); +}); + +/** + * Unset properties on a group permanently. + * + * ### Usage: + * + * mixpanel.get_group('company', 'mixpanel').unset('Founded'); + * + * @param {String} prop The name of the property. + * @param {Function} [callback] If provided, the callback will be called after the tracking event + */ +MixpanelGroup.prototype.unset = addOptOutCheckMixpanelGroup(function(prop, callback) { + var data = this.unset_action(prop); + return this._send_request(data, callback); +}); + +/** + * Merge a given list with a list-valued group property, excluding duplicate values. + * + * ### Usage: + * + * // merge a value to a list, creating it if needed + * mixpanel.get_group('company', 'mixpanel').union('Location', ['San Francisco', 'London']); + * + * @param {String} list_name Name of the property. + * @param {Array} values Values to merge with the given property + * @param {Function} [callback] If provided, the callback will be called after the tracking event + */ +MixpanelGroup.prototype.union = addOptOutCheckMixpanelGroup(function(list_name, values, callback) { + if (_.isObject(list_name)) { + callback = values; + } + var data = this.union_action(list_name, values); + return this._send_request(data, callback); +}); + +/** + * Permanently delete a group. + * + * ### Usage: + * + * mixpanel.get_group('company', 'mixpanel').delete(); + * + * @param {Function} [callback] If provided, the callback will be called after the tracking event + */ +MixpanelGroup.prototype['delete'] = addOptOutCheckMixpanelGroup(function(callback) { + // bracket notation above prevents a minification error related to reserved words + var data = this.delete_action(); + return this._send_request(data, callback); +}); + +/** + * Remove a property from a group. The value will be ignored if doesn't exist. + * + * ### Usage: + * + * mixpanel.get_group('company', 'mixpanel').remove('Location', 'London'); + * + * @param {String} list_name Name of the property. + * @param {Object} value Value to remove from the given group property + * @param {Function} [callback] If provided, the callback will be called after the tracking event + */ +MixpanelGroup.prototype.remove = addOptOutCheckMixpanelGroup(function(list_name, value, callback) { + var data = this.remove_action(list_name, value); + return this._send_request(data, callback); +}); + +MixpanelGroup.prototype._send_request = function(data, callback) { + data['$group_key'] = this._group_key; + data['$group_id'] = this._group_id; + data['$token'] = this._get_config('token'); + + var date_encoded_data = _.encodeDates(data); + return this._mixpanel._track_or_batch({ + type: 'groups', + data: date_encoded_data, + endpoint: this._get_config('api_host') + '/groups/', + batcher: this._mixpanel.request_batchers.groups + }, callback); +}; + +MixpanelGroup.prototype._is_reserved_property = function(prop) { + return prop === '$group_key' || prop === '$group_id'; +}; + +MixpanelGroup.prototype._get_config = function(conf) { + return this._mixpanel.get_config(conf); +}; + +MixpanelGroup.prototype.toString = function() { + return this._mixpanel.toString() + '.group.' + this._group_key + '.' + this._group_id; +}; + +// MixpanelGroup Exports +MixpanelGroup.prototype['remove'] = MixpanelGroup.prototype.remove; +MixpanelGroup.prototype['set'] = MixpanelGroup.prototype.set; +MixpanelGroup.prototype['set_once'] = MixpanelGroup.prototype.set_once; +MixpanelGroup.prototype['union'] = MixpanelGroup.prototype.union; +MixpanelGroup.prototype['unset'] = MixpanelGroup.prototype.unset; +MixpanelGroup.prototype['toString'] = MixpanelGroup.prototype.toString; + +/** + * Mixpanel People Object + * @constructor + */ +var MixpanelPeople = function() {}; + +_.extend(MixpanelPeople.prototype, apiActions); + +MixpanelPeople.prototype._init = function(mixpanel_instance) { + this._mixpanel = mixpanel_instance; +}; + +/* +* Set properties on a user record. +* +* ### Usage: +* +* mixpanel.people.set('gender', 'm'); +* +* // or set multiple properties at once +* mixpanel.people.set({ +* 'Company': 'Acme', +* 'Plan': 'Premium', +* 'Upgrade date': new Date() +* }); +* // properties can be strings, integers, dates, or lists +* +* @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values. +* @param {*} [to] A value to set on the given property name +* @param {Function} [callback] If provided, the callback will be called after tracking the event. +*/ +MixpanelPeople.prototype.set = addOptOutCheckMixpanelPeople(function(prop, to, callback) { + var data = this.set_action(prop, to); + if (_.isObject(prop)) { + callback = to; + } + // make sure that the referrer info has been updated and saved + if (this._get_config('save_referrer')) { + this._mixpanel['persistence'].update_referrer_info(document.referrer); + } + + // update $set object with default people properties + data[SET_ACTION] = _.extend( + {}, + _.info.people_properties(), + this._mixpanel['persistence'].get_referrer_info(), + data[SET_ACTION] + ); + return this._send_request(data, callback); +}); + +/* +* Set properties on a user record, only if they do not yet exist. +* This will not overwrite previous people property values, unlike +* people.set(). +* +* ### Usage: +* +* mixpanel.people.set_once('First Login Date', new Date()); +* +* // or set multiple properties at once +* mixpanel.people.set_once({ +* 'First Login Date': new Date(), +* 'Starting Plan': 'Premium' +* }); +* +* // properties can be strings, integers or dates +* +* @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values. +* @param {*} [to] A value to set on the given property name +* @param {Function} [callback] If provided, the callback will be called after tracking the event. +*/ +MixpanelPeople.prototype.set_once = addOptOutCheckMixpanelPeople(function(prop, to, callback) { + var data = this.set_once_action(prop, to); + if (_.isObject(prop)) { + callback = to; + } + return this._send_request(data, callback); +}); + +/* +* Unset properties on a user record (permanently removes the properties and their values from a profile). +* +* ### Usage: +* +* mixpanel.people.unset('gender'); +* +* // or unset multiple properties at once +* mixpanel.people.unset(['gender', 'Company']); +* +* @param {Array|String} prop If a string, this is the name of the property. If an array, this is a list of property names. +* @param {Function} [callback] If provided, the callback will be called after tracking the event. +*/ +MixpanelPeople.prototype.unset = addOptOutCheckMixpanelPeople(function(prop, callback) { + var data = this.unset_action(prop); + return this._send_request(data, callback); +}); + +/* +* Increment/decrement numeric people analytics properties. +* +* ### Usage: +* +* mixpanel.people.increment('page_views', 1); +* +* // or, for convenience, if you're just incrementing a counter by +* // 1, you can simply do +* mixpanel.people.increment('page_views'); +* +* // to decrement a counter, pass a negative number +* mixpanel.people.increment('credits_left', -1); +* +* // like mixpanel.people.set(), you can increment multiple +* // properties at once: +* mixpanel.people.increment({ +* counter1: 1, +* counter2: 6 +* }); +* +* @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and numeric values. +* @param {Number} [by] An amount to increment the given property +* @param {Function} [callback] If provided, the callback will be called after tracking the event. +*/ +MixpanelPeople.prototype.increment = addOptOutCheckMixpanelPeople(function(prop, by, callback) { + var data = {}; + var $add = {}; + if (_.isObject(prop)) { + _.each(prop, function(v, k) { + if (!this._is_reserved_property(k)) { + if (isNaN(parseFloat(v))) { + console$1.error('Invalid increment value passed to mixpanel.people.increment - must be a number'); + return; + } else { + $add[k] = v; + } + } + }, this); + callback = by; + } else { + // convenience: mixpanel.people.increment('property'); will + // increment 'property' by 1 + if (_.isUndefined(by)) { + by = 1; + } + $add[prop] = by; + } + data[ADD_ACTION] = $add; + + return this._send_request(data, callback); +}); + +/* +* Append a value to a list-valued people analytics property. +* +* ### Usage: +* +* // append a value to a list, creating it if needed +* mixpanel.people.append('pages_visited', 'homepage'); +* +* // like mixpanel.people.set(), you can append multiple +* // properties at once: +* mixpanel.people.append({ +* list1: 'bob', +* list2: 123 +* }); +* +* @param {Object|String} list_name If a string, this is the name of the property. If an object, this is an associative array of names and values. +* @param {*} [value] value An item to append to the list +* @param {Function} [callback] If provided, the callback will be called after tracking the event. +*/ +MixpanelPeople.prototype.append = addOptOutCheckMixpanelPeople(function(list_name, value, callback) { + if (_.isObject(list_name)) { + callback = value; + } + var data = this.append_action(list_name, value); + return this._send_request(data, callback); +}); + +/* +* Remove a value from a list-valued people analytics property. +* +* ### Usage: +* +* mixpanel.people.remove('School', 'UCB'); +* +* @param {Object|String} list_name If a string, this is the name of the property. If an object, this is an associative array of names and values. +* @param {*} [value] value Item to remove from the list +* @param {Function} [callback] If provided, the callback will be called after tracking the event. +*/ +MixpanelPeople.prototype.remove = addOptOutCheckMixpanelPeople(function(list_name, value, callback) { + if (_.isObject(list_name)) { + callback = value; + } + var data = this.remove_action(list_name, value); + return this._send_request(data, callback); +}); + +/* +* Merge a given list with a list-valued people analytics property, +* excluding duplicate values. +* +* ### Usage: +* +* // merge a value to a list, creating it if needed +* mixpanel.people.union('pages_visited', 'homepage'); +* +* // like mixpanel.people.set(), you can append multiple +* // properties at once: +* mixpanel.people.union({ +* list1: 'bob', +* list2: 123 +* }); +* +* // like mixpanel.people.append(), you can append multiple +* // values to the same list: +* mixpanel.people.union({ +* list1: ['bob', 'billy'] +* }); +* +* @param {Object|String} list_name If a string, this is the name of the property. If an object, this is an associative array of names and values. +* @param {*} [value] Value / values to merge with the given property +* @param {Function} [callback] If provided, the callback will be called after tracking the event. +*/ +MixpanelPeople.prototype.union = addOptOutCheckMixpanelPeople(function(list_name, values, callback) { + if (_.isObject(list_name)) { + callback = values; + } + var data = this.union_action(list_name, values); + return this._send_request(data, callback); +}); + +/* + * Record that you have charged the current user a certain amount + * of money. Charges recorded with track_charge() will appear in the + * Mixpanel revenue report. + * + * ### Usage: + * + * // charge a user $50 + * mixpanel.people.track_charge(50); + * + * // charge a user $30.50 on the 2nd of january + * mixpanel.people.track_charge(30.50, { + * '$time': new Date('jan 1 2012') + * }); + * + * @param {Number} amount The amount of money charged to the current user + * @param {Object} [properties] An associative array of properties associated with the charge + * @param {Function} [callback] If provided, the callback will be called when the server responds + * @deprecated + */ +MixpanelPeople.prototype.track_charge = addOptOutCheckMixpanelPeople(function(amount, properties, callback) { + if (!_.isNumber(amount)) { + amount = parseFloat(amount); + if (isNaN(amount)) { + console$1.error('Invalid value passed to mixpanel.people.track_charge - must be a number'); + return; + } + } + + return this.append('$transactions', _.extend({ + '$amount': amount + }, properties), callback); +}); + +/* + * Permanently clear all revenue report transactions from the + * current user's people analytics profile. + * + * ### Usage: + * + * mixpanel.people.clear_charges(); + * + * @param {Function} [callback] If provided, the callback will be called after tracking the event. + * @deprecated + */ +MixpanelPeople.prototype.clear_charges = function(callback) { + return this.set('$transactions', [], callback); +}; + +/* +* Permanently deletes the current people analytics profile from +* Mixpanel (using the current distinct_id). +* +* ### Usage: +* +* // remove the all data you have stored about the current user +* mixpanel.people.delete_user(); +* +*/ +MixpanelPeople.prototype.delete_user = function() { + if (!this._identify_called()) { + console$1.error('mixpanel.people.delete_user() requires you to call identify() first'); + return; + } + var data = {'$delete': this._mixpanel.get_distinct_id()}; + return this._send_request(data); +}; + +MixpanelPeople.prototype.toString = function() { + return this._mixpanel.toString() + '.people'; +}; + +MixpanelPeople.prototype._send_request = function(data, callback) { + data['$token'] = this._get_config('token'); + data['$distinct_id'] = this._mixpanel.get_distinct_id(); + var device_id = this._mixpanel.get_property('$device_id'); + var user_id = this._mixpanel.get_property('$user_id'); + var had_persisted_distinct_id = this._mixpanel.get_property('$had_persisted_distinct_id'); + if (device_id) { + data['$device_id'] = device_id; + } + if (user_id) { + data['$user_id'] = user_id; + } + if (had_persisted_distinct_id) { + data['$had_persisted_distinct_id'] = had_persisted_distinct_id; + } + + var date_encoded_data = _.encodeDates(data); + + if (!this._identify_called()) { + this._enqueue(data); + if (!_.isUndefined(callback)) { + if (this._get_config('verbose')) { + callback({status: -1, error: null}); + } else { + callback(-1); + } + } + return _.truncate(date_encoded_data, 255); + } + + return this._mixpanel._track_or_batch({ + type: 'people', + data: date_encoded_data, + endpoint: this._get_config('api_host') + '/engage/', + batcher: this._mixpanel.request_batchers.people + }, callback); +}; + +MixpanelPeople.prototype._get_config = function(conf_var) { + return this._mixpanel.get_config(conf_var); +}; + +MixpanelPeople.prototype._identify_called = function() { + return this._mixpanel._flags.identify_called === true; +}; + +// Queue up engage operations if identify hasn't been called yet. +MixpanelPeople.prototype._enqueue = function(data) { + if (SET_ACTION in data) { + this._mixpanel['persistence']._add_to_people_queue(SET_ACTION, data); + } else if (SET_ONCE_ACTION in data) { + this._mixpanel['persistence']._add_to_people_queue(SET_ONCE_ACTION, data); + } else if (UNSET_ACTION in data) { + this._mixpanel['persistence']._add_to_people_queue(UNSET_ACTION, data); + } else if (ADD_ACTION in data) { + this._mixpanel['persistence']._add_to_people_queue(ADD_ACTION, data); + } else if (APPEND_ACTION in data) { + this._mixpanel['persistence']._add_to_people_queue(APPEND_ACTION, data); + } else if (REMOVE_ACTION in data) { + this._mixpanel['persistence']._add_to_people_queue(REMOVE_ACTION, data); + } else if (UNION_ACTION in data) { + this._mixpanel['persistence']._add_to_people_queue(UNION_ACTION, data); + } else { + console$1.error('Invalid call to _enqueue():', data); + } +}; + +MixpanelPeople.prototype._flush_one_queue = function(action, action_method, callback, queue_to_params_fn) { + var _this = this; + var queued_data = _.extend({}, this._mixpanel['persistence']._get_queue(action)); + var action_params = queued_data; + + if (!_.isUndefined(queued_data) && _.isObject(queued_data) && !_.isEmptyObject(queued_data)) { + _this._mixpanel['persistence']._pop_from_people_queue(action, queued_data); + if (queue_to_params_fn) { + action_params = queue_to_params_fn(queued_data); + } + action_method.call(_this, action_params, function(response, data) { + // on bad response, we want to add it back to the queue + if (response === 0) { + _this._mixpanel['persistence']._add_to_people_queue(action, queued_data); + } + if (!_.isUndefined(callback)) { + callback(response, data); + } + }); + } +}; + +// Flush queued engage operations - order does not matter, +// and there are network level race conditions anyway +MixpanelPeople.prototype._flush = function( + _set_callback, _add_callback, _append_callback, _set_once_callback, _union_callback, _unset_callback, _remove_callback +) { + var _this = this; + var $append_queue = this._mixpanel['persistence']._get_queue(APPEND_ACTION); + var $remove_queue = this._mixpanel['persistence']._get_queue(REMOVE_ACTION); + + this._flush_one_queue(SET_ACTION, this.set, _set_callback); + this._flush_one_queue(SET_ONCE_ACTION, this.set_once, _set_once_callback); + this._flush_one_queue(UNSET_ACTION, this.unset, _unset_callback, function(queue) { return _.keys(queue); }); + this._flush_one_queue(ADD_ACTION, this.increment, _add_callback); + this._flush_one_queue(UNION_ACTION, this.union, _union_callback); + + // we have to fire off each $append individually since there is + // no concat method server side + if (!_.isUndefined($append_queue) && _.isArray($append_queue) && $append_queue.length) { + var $append_item; + var append_callback = function(response, data) { + if (response === 0) { + _this._mixpanel['persistence']._add_to_people_queue(APPEND_ACTION, $append_item); + } + if (!_.isUndefined(_append_callback)) { + _append_callback(response, data); + } + }; + for (var i = $append_queue.length - 1; i >= 0; i--) { + $append_item = $append_queue.pop(); + if (!_.isEmptyObject($append_item)) { + _this.append($append_item, append_callback); + } + } + // Save the shortened append queue + _this._mixpanel['persistence'].save(); + } + + // same for $remove + if (!_.isUndefined($remove_queue) && _.isArray($remove_queue) && $remove_queue.length) { + var $remove_item; + var remove_callback = function(response, data) { + if (response === 0) { + _this._mixpanel['persistence']._add_to_people_queue(REMOVE_ACTION, $remove_item); + } + if (!_.isUndefined(_remove_callback)) { + _remove_callback(response, data); + } + }; + for (var j = $remove_queue.length - 1; j >= 0; j--) { + $remove_item = $remove_queue.pop(); + if (!_.isEmptyObject($remove_item)) { + _this.remove($remove_item, remove_callback); + } + } + _this._mixpanel['persistence'].save(); + } +}; + +MixpanelPeople.prototype._is_reserved_property = function(prop) { + return prop === '$distinct_id' || prop === '$token' || prop === '$device_id' || prop === '$user_id' || prop === '$had_persisted_distinct_id'; +}; + +// MixpanelPeople Exports +MixpanelPeople.prototype['set'] = MixpanelPeople.prototype.set; +MixpanelPeople.prototype['set_once'] = MixpanelPeople.prototype.set_once; +MixpanelPeople.prototype['unset'] = MixpanelPeople.prototype.unset; +MixpanelPeople.prototype['increment'] = MixpanelPeople.prototype.increment; +MixpanelPeople.prototype['append'] = MixpanelPeople.prototype.append; +MixpanelPeople.prototype['remove'] = MixpanelPeople.prototype.remove; +MixpanelPeople.prototype['union'] = MixpanelPeople.prototype.union; +MixpanelPeople.prototype['track_charge'] = MixpanelPeople.prototype.track_charge; +MixpanelPeople.prototype['clear_charges'] = MixpanelPeople.prototype.clear_charges; +MixpanelPeople.prototype['delete_user'] = MixpanelPeople.prototype.delete_user; +MixpanelPeople.prototype['toString'] = MixpanelPeople.prototype.toString; + +/* + * Constants + */ +/** @const */ var SET_QUEUE_KEY = '__mps'; +/** @const */ var SET_ONCE_QUEUE_KEY = '__mpso'; +/** @const */ var UNSET_QUEUE_KEY = '__mpus'; +/** @const */ var ADD_QUEUE_KEY = '__mpa'; +/** @const */ var APPEND_QUEUE_KEY = '__mpap'; +/** @const */ var REMOVE_QUEUE_KEY = '__mpr'; +/** @const */ var UNION_QUEUE_KEY = '__mpu'; +// This key is deprecated, but we want to check for it to see whether aliasing is allowed. +/** @const */ var PEOPLE_DISTINCT_ID_KEY = '$people_distinct_id'; +/** @const */ var ALIAS_ID_KEY = '__alias'; +/** @const */ var EVENT_TIMERS_KEY = '__timers'; +/** @const */ var RESERVED_PROPERTIES = [ + SET_QUEUE_KEY, + SET_ONCE_QUEUE_KEY, + UNSET_QUEUE_KEY, + ADD_QUEUE_KEY, + APPEND_QUEUE_KEY, + REMOVE_QUEUE_KEY, + UNION_QUEUE_KEY, + PEOPLE_DISTINCT_ID_KEY, + ALIAS_ID_KEY, + EVENT_TIMERS_KEY +]; + +/** + * Mixpanel Persistence Object + * @constructor + */ +var MixpanelPersistence = function(config) { + this['props'] = {}; + this.campaign_params_saved = false; + + if (config['persistence_name']) { + this.name = 'mp_' + config['persistence_name']; + } else { + this.name = 'mp_' + config['token'] + '_mixpanel'; + } + + var storage_type = config['persistence']; + if (storage_type !== 'cookie' && storage_type !== 'localStorage') { + console$1.critical('Unknown persistence type ' + storage_type + '; falling back to cookie'); + storage_type = config['persistence'] = 'cookie'; + } + + if (storage_type === 'localStorage' && _.localStorage.is_supported()) { + this.storage = _.localStorage; + } else { + this.storage = _.cookie; + } + + this.load(); + this.update_config(config); + this.upgrade(config); + this.save(); +}; + +MixpanelPersistence.prototype.properties = function() { + var p = {}; + // Filter out reserved properties + _.each(this['props'], function(v, k) { + if (!_.include(RESERVED_PROPERTIES, k)) { + p[k] = v; + } + }); + return p; +}; + +MixpanelPersistence.prototype.load = function() { + if (this.disabled) { return; } + + var entry = this.storage.parse(this.name); + + if (entry) { + this['props'] = _.extend({}, entry); + } +}; + +MixpanelPersistence.prototype.upgrade = function(config) { + var upgrade_from_old_lib = config['upgrade'], + old_cookie_name, + old_cookie; + + if (upgrade_from_old_lib) { + old_cookie_name = 'mp_super_properties'; + // Case where they had a custom cookie name before. + if (typeof(upgrade_from_old_lib) === 'string') { + old_cookie_name = upgrade_from_old_lib; + } + + old_cookie = this.storage.parse(old_cookie_name); + + // remove the cookie + this.storage.remove(old_cookie_name); + this.storage.remove(old_cookie_name, true); + + if (old_cookie) { + this['props'] = _.extend( + this['props'], + old_cookie['all'], + old_cookie['events'] + ); + } + } + + if (!config['cookie_name'] && config['name'] !== 'mixpanel') { + // special case to handle people with cookies of the form + // mp_TOKEN_INSTANCENAME from the first release of this library + old_cookie_name = 'mp_' + config['token'] + '_' + config['name']; + old_cookie = this.storage.parse(old_cookie_name); + + if (old_cookie) { + this.storage.remove(old_cookie_name); + this.storage.remove(old_cookie_name, true); + + // Save the prop values that were in the cookie from before - + // this should only happen once as we delete the old one. + this.register_once(old_cookie); + } + } + + if (this.storage === _.localStorage) { + old_cookie = _.cookie.parse(this.name); + + _.cookie.remove(this.name); + _.cookie.remove(this.name, true); + + if (old_cookie) { + this.register_once(old_cookie); + } + } +}; + +MixpanelPersistence.prototype.save = function() { + if (this.disabled) { return; } + this.storage.set( + this.name, + _.JSONEncode(this['props']), + this.expire_days, + this.cross_subdomain, + this.secure, + this.cross_site, + this.cookie_domain + ); +}; + +MixpanelPersistence.prototype.remove = function() { + // remove both domain and subdomain cookies + this.storage.remove(this.name, false, this.cookie_domain); + this.storage.remove(this.name, true, this.cookie_domain); +}; + +// removes the storage entry and deletes all loaded data +// forced name for tests +MixpanelPersistence.prototype.clear = function() { + this.remove(); + this['props'] = {}; +}; + +/** +* @param {Object} props +* @param {*=} default_value +* @param {number=} days +*/ +MixpanelPersistence.prototype.register_once = function(props, default_value, days) { + if (_.isObject(props)) { + if (typeof(default_value) === 'undefined') { default_value = 'None'; } + this.expire_days = (typeof(days) === 'undefined') ? this.default_expiry : days; + + _.each(props, function(val, prop) { + if (!this['props'].hasOwnProperty(prop) || this['props'][prop] === default_value) { + this['props'][prop] = val; + } + }, this); + + this.save(); + + return true; + } + return false; +}; + +/** +* @param {Object} props +* @param {number=} days +*/ +MixpanelPersistence.prototype.register = function(props, days) { + if (_.isObject(props)) { + this.expire_days = (typeof(days) === 'undefined') ? this.default_expiry : days; + + _.extend(this['props'], props); + + this.save(); + + return true; + } + return false; +}; + +MixpanelPersistence.prototype.unregister = function(prop) { + if (prop in this['props']) { + delete this['props'][prop]; + this.save(); + } +}; + +MixpanelPersistence.prototype.update_search_keyword = function(referrer) { + this.register(_.info.searchInfo(referrer)); +}; + +// EXPORTED METHOD, we test this directly. +MixpanelPersistence.prototype.update_referrer_info = function(referrer) { + // If referrer doesn't exist, we want to note the fact that it was type-in traffic. + this.register_once({ + '$initial_referrer': referrer || '$direct', + '$initial_referring_domain': _.info.referringDomain(referrer) || '$direct' + }, ''); +}; + +MixpanelPersistence.prototype.get_referrer_info = function() { + return _.strip_empty_properties({ + '$initial_referrer': this['props']['$initial_referrer'], + '$initial_referring_domain': this['props']['$initial_referring_domain'] + }); +}; + +// safely fills the passed in object with stored properties, +// does not override any properties defined in both +// returns the passed in object +MixpanelPersistence.prototype.safe_merge = function(props) { + _.each(this['props'], function(val, prop) { + if (!(prop in props)) { + props[prop] = val; + } + }); + + return props; +}; + +MixpanelPersistence.prototype.update_config = function(config) { + this.default_expiry = this.expire_days = config['cookie_expiration']; + this.set_disabled(config['disable_persistence']); + this.set_cookie_domain(config['cookie_domain']); + this.set_cross_site(config['cross_site_cookie']); + this.set_cross_subdomain(config['cross_subdomain_cookie']); + this.set_secure(config['secure_cookie']); +}; + +MixpanelPersistence.prototype.set_disabled = function(disabled) { + this.disabled = disabled; + if (this.disabled) { + this.remove(); + } else { + this.save(); + } +}; + +MixpanelPersistence.prototype.set_cookie_domain = function(cookie_domain) { + if (cookie_domain !== this.cookie_domain) { + this.remove(); + this.cookie_domain = cookie_domain; + this.save(); + } +}; + +MixpanelPersistence.prototype.set_cross_site = function(cross_site) { + if (cross_site !== this.cross_site) { + this.cross_site = cross_site; + this.remove(); + this.save(); + } +}; + +MixpanelPersistence.prototype.set_cross_subdomain = function(cross_subdomain) { + if (cross_subdomain !== this.cross_subdomain) { + this.cross_subdomain = cross_subdomain; + this.remove(); + this.save(); + } +}; + +MixpanelPersistence.prototype.get_cross_subdomain = function() { + return this.cross_subdomain; +}; + +MixpanelPersistence.prototype.set_secure = function(secure) { + if (secure !== this.secure) { + this.secure = secure ? true : false; + this.remove(); + this.save(); + } +}; + +MixpanelPersistence.prototype._add_to_people_queue = function(queue, data) { + var q_key = this._get_queue_key(queue), + q_data = data[queue], + set_q = this._get_or_create_queue(SET_ACTION), + set_once_q = this._get_or_create_queue(SET_ONCE_ACTION), + unset_q = this._get_or_create_queue(UNSET_ACTION), + add_q = this._get_or_create_queue(ADD_ACTION), + union_q = this._get_or_create_queue(UNION_ACTION), + remove_q = this._get_or_create_queue(REMOVE_ACTION, []), + append_q = this._get_or_create_queue(APPEND_ACTION, []); + + if (q_key === SET_QUEUE_KEY) { + // Update the set queue - we can override any existing values + _.extend(set_q, q_data); + // if there was a pending increment, override it + // with the set. + this._pop_from_people_queue(ADD_ACTION, q_data); + // if there was a pending union, override it + // with the set. + this._pop_from_people_queue(UNION_ACTION, q_data); + this._pop_from_people_queue(UNSET_ACTION, q_data); + } else if (q_key === SET_ONCE_QUEUE_KEY) { + // only queue the data if there is not already a set_once call for it. + _.each(q_data, function(v, k) { + if (!(k in set_once_q)) { + set_once_q[k] = v; + } + }); + this._pop_from_people_queue(UNSET_ACTION, q_data); + } else if (q_key === UNSET_QUEUE_KEY) { + _.each(q_data, function(prop) { + + // undo previously-queued actions on this key + _.each([set_q, set_once_q, add_q, union_q], function(enqueued_obj) { + if (prop in enqueued_obj) { + delete enqueued_obj[prop]; + } + }); + _.each(append_q, function(append_obj) { + if (prop in append_obj) { + delete append_obj[prop]; + } + }); + + unset_q[prop] = true; + + }); + } else if (q_key === ADD_QUEUE_KEY) { + _.each(q_data, function(v, k) { + // If it exists in the set queue, increment + // the value + if (k in set_q) { + set_q[k] += v; + } else { + // If it doesn't exist, update the add + // queue + if (!(k in add_q)) { + add_q[k] = 0; + } + add_q[k] += v; + } + }, this); + this._pop_from_people_queue(UNSET_ACTION, q_data); + } else if (q_key === UNION_QUEUE_KEY) { + _.each(q_data, function(v, k) { + if (_.isArray(v)) { + if (!(k in union_q)) { + union_q[k] = []; + } + // We may send duplicates, the server will dedup them. + union_q[k] = union_q[k].concat(v); + } + }); + this._pop_from_people_queue(UNSET_ACTION, q_data); + } else if (q_key === REMOVE_QUEUE_KEY) { + remove_q.push(q_data); + this._pop_from_people_queue(APPEND_ACTION, q_data); + } else if (q_key === APPEND_QUEUE_KEY) { + append_q.push(q_data); + this._pop_from_people_queue(UNSET_ACTION, q_data); + } + + console$1.log('MIXPANEL PEOPLE REQUEST (QUEUED, PENDING IDENTIFY):'); + console$1.log(data); + + this.save(); +}; + +MixpanelPersistence.prototype._pop_from_people_queue = function(queue, data) { + var q = this._get_queue(queue); + if (!_.isUndefined(q)) { + _.each(data, function(v, k) { + if (queue === APPEND_ACTION || queue === REMOVE_ACTION) { + // list actions: only remove if both k+v match + // e.g. remove should not override append in a case like + // append({foo: 'bar'}); remove({foo: 'qux'}) + _.each(q, function(queued_action) { + if (queued_action[k] === v) { + delete queued_action[k]; + } + }); + } else { + delete q[k]; + } + }, this); + + this.save(); + } +}; + +MixpanelPersistence.prototype._get_queue_key = function(queue) { + if (queue === SET_ACTION) { + return SET_QUEUE_KEY; + } else if (queue === SET_ONCE_ACTION) { + return SET_ONCE_QUEUE_KEY; + } else if (queue === UNSET_ACTION) { + return UNSET_QUEUE_KEY; + } else if (queue === ADD_ACTION) { + return ADD_QUEUE_KEY; + } else if (queue === APPEND_ACTION) { + return APPEND_QUEUE_KEY; + } else if (queue === REMOVE_ACTION) { + return REMOVE_QUEUE_KEY; + } else if (queue === UNION_ACTION) { + return UNION_QUEUE_KEY; + } else { + console$1.error('Invalid queue:', queue); + } +}; + +MixpanelPersistence.prototype._get_queue = function(queue) { + return this['props'][this._get_queue_key(queue)]; +}; +MixpanelPersistence.prototype._get_or_create_queue = function(queue, default_val) { + var key = this._get_queue_key(queue); + default_val = _.isUndefined(default_val) ? {} : default_val; + + return this['props'][key] || (this['props'][key] = default_val); +}; + +MixpanelPersistence.prototype.set_event_timer = function(event_name, timestamp) { + var timers = this['props'][EVENT_TIMERS_KEY] || {}; + timers[event_name] = timestamp; + this['props'][EVENT_TIMERS_KEY] = timers; + this.save(); +}; + +MixpanelPersistence.prototype.remove_event_timer = function(event_name) { + var timers = this['props'][EVENT_TIMERS_KEY] || {}; + var timestamp = timers[event_name]; + if (!_.isUndefined(timestamp)) { + delete this['props'][EVENT_TIMERS_KEY][event_name]; + this.save(); + } + return timestamp; +}; + +/* + * Mixpanel JS Library + * + * Copyright 2012, Mixpanel, Inc. All Rights Reserved + * http://mixpanel.com/ + * + * Includes portions of Underscore.js + * http://documentcloud.github.com/underscore/ + * (c) 2011 Jeremy Ashkenas, DocumentCloud Inc. + * Released under the MIT License. + */ + +// ==ClosureCompiler== +// @compilation_level ADVANCED_OPTIMIZATIONS +// @output_file_name mixpanel-2.8.min.js +// ==/ClosureCompiler== + +/* +SIMPLE STYLE GUIDE: + +this.x === public function +this._x === internal - only use within this file +this.__x === private - only use within the class + +Globals should be all caps +*/ + +var init_type; // MODULE or SNIPPET loader +var mixpanel_master; // main mixpanel instance / object +var INIT_MODULE = 0; +var INIT_SNIPPET = 1; + +var IDENTITY_FUNC = function(x) {return x;}; +var NOOP_FUNC = function() {}; + +/** @const */ var PRIMARY_INSTANCE_NAME = 'mixpanel'; +/** @const */ var PAYLOAD_TYPE_BASE64 = 'base64'; +/** @const */ var PAYLOAD_TYPE_JSON = 'json'; +/** @const */ var DEVICE_ID_PREFIX = '$device:'; + + +/* + * Dynamic... constants? Is that an oxymoron? + */ +// http://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/ +// https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#withCredentials +var USE_XHR = (window$1.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest()); + +// IE<10 does not support cross-origin XHR's but script tags +// with defer won't block window.onload; ENQUEUE_REQUESTS +// should only be true for Opera<12 +var ENQUEUE_REQUESTS = !USE_XHR && (userAgent.indexOf('MSIE') === -1) && (userAgent.indexOf('Mozilla') === -1); + +// save reference to navigator.sendBeacon so it can be minified +var sendBeacon = null; +if (navigator['sendBeacon']) { + sendBeacon = function() { + // late reference to navigator.sendBeacon to allow patching/spying + return navigator['sendBeacon'].apply(navigator, arguments); + }; +} + +/* + * Module-level globals + */ +var DEFAULT_CONFIG = { + 'api_host': 'https://api-js.mixpanel.com', + 'api_method': 'POST', + 'api_transport': 'XHR', + 'api_payload_format': PAYLOAD_TYPE_BASE64, + 'app_host': 'https://mixpanel.com', + 'cdn': 'https://cdn.mxpnl.com', + 'cross_site_cookie': false, + 'cross_subdomain_cookie': true, + 'error_reporter': NOOP_FUNC, + 'persistence': 'cookie', + 'persistence_name': '', + 'cookie_domain': '', + 'cookie_name': '', + 'loaded': NOOP_FUNC, + 'track_marketing': true, + 'track_pageview': false, + 'skip_first_touch_marketing': false, + 'store_google': true, + 'save_referrer': true, + 'test': false, + 'verbose': false, + 'img': false, + 'debug': false, + 'track_links_timeout': 300, + 'cookie_expiration': 365, + 'upgrade': false, + 'disable_persistence': false, + 'disable_cookie': false, + 'secure_cookie': false, + 'ip': true, + 'opt_out_tracking_by_default': false, + 'opt_out_persistence_by_default': false, + 'opt_out_tracking_persistence_type': 'localStorage', + 'opt_out_tracking_cookie_prefix': null, + 'property_blacklist': [], + 'xhr_headers': {}, // { header: value, header2: value } + 'ignore_dnt': false, + 'batch_requests': true, + 'batch_size': 50, + 'batch_flush_interval_ms': 5000, + 'batch_request_timeout_ms': 90000, + 'batch_autostart': true, + 'hooks': {} +}; + +var DOM_LOADED = false; + +/** + * Mixpanel Library Object + * @constructor + */ +var MixpanelLib = function() {}; + + +/** + * create_mplib(token:string, config:object, name:string) + * + * This function is used by the init method of MixpanelLib objects + * as well as the main initializer at the end of the JSLib (that + * initializes document.mixpanel as well as any additional instances + * declared before this file has loaded). + */ +var create_mplib = function(token, config, name) { + var instance, + target = (name === PRIMARY_INSTANCE_NAME) ? mixpanel_master : mixpanel_master[name]; + + if (target && init_type === INIT_MODULE) { + instance = target; + } else { + if (target && !_.isArray(target)) { + console$1.error('You have already initialized ' + name); + return; + } + instance = new MixpanelLib(); + } + + instance._cached_groups = {}; // cache groups in a pool + + instance._init(token, config, name); + + instance['people'] = new MixpanelPeople(); + instance['people']._init(instance); + + if (!instance.get_config('skip_first_touch_marketing')) { + // We need null UTM params in the object because + // UTM parameters act as a tuple. If any UTM param + // is present, then we set all UTM params including + // empty ones together + var utm_params = _.info.campaignParams(null); + var initial_utm_params = {}; + var has_utm = false; + _.each(utm_params, function(utm_value, utm_key) { + initial_utm_params['initial_' + utm_key] = utm_value; + if (utm_value) { + has_utm = true; + } + }); + if (has_utm) { + instance['people'].set_once(initial_utm_params); + } + } + + // if any instance on the page has debug = true, we set the + // global debug to be true + Config.DEBUG = Config.DEBUG || instance.get_config('debug'); + + // if target is not defined, we called init after the lib already + // loaded, so there won't be an array of things to execute + if (!_.isUndefined(target) && _.isArray(target)) { + // Crunch through the people queue first - we queue this data up & + // flush on identify, so it's better to do all these operations first + instance._execute_array.call(instance['people'], target['people']); + instance._execute_array(target); + } + + return instance; +}; + +// Initialization methods + +/** + * This function initializes a new instance of the Mixpanel tracking object. + * All new instances are added to the main mixpanel object as sub properties (such as + * mixpanel.library_name) and also returned by this function. To define a + * second instance on the page, you would call: + * + * mixpanel.init('new token', { your: 'config' }, 'library_name'); + * + * and use it like so: + * + * mixpanel.library_name.track(...); + * + * @param {String} token Your Mixpanel API token + * @param {Object} [config] A dictionary of config options to override. See a list of default config options. + * @param {String} [name] The name for the new mixpanel instance that you want created + */ +MixpanelLib.prototype.init = function (token, config, name) { + if (_.isUndefined(name)) { + this.report_error('You must name your new library: init(token, config, name)'); + return; + } + if (name === PRIMARY_INSTANCE_NAME) { + this.report_error('You must initialize the main mixpanel object right after you include the Mixpanel js snippet'); + return; + } + + var instance = create_mplib(token, config, name); + mixpanel_master[name] = instance; + instance._loaded(); + + return instance; +}; + +// mixpanel._init(token:string, config:object, name:string) +// +// This function sets up the current instance of the mixpanel +// library. The difference between this method and the init(...) +// method is this one initializes the actual instance, whereas the +// init(...) method sets up a new library and calls _init on it. +// +MixpanelLib.prototype._init = function(token, config, name) { + config = config || {}; + + this['__loaded'] = true; + this['config'] = {}; + + var variable_features = {}; + + // default to JSON payload for standard mixpanel.com API hosts + if (!('api_payload_format' in config)) { + var api_host = config['api_host'] || DEFAULT_CONFIG['api_host']; + if (api_host.match(/\.mixpanel\.com/)) { + variable_features['api_payload_format'] = PAYLOAD_TYPE_JSON; + } + } + + this.set_config(_.extend({}, DEFAULT_CONFIG, variable_features, config, { + 'name': name, + 'token': token, + 'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc' + })); + + this['_jsc'] = NOOP_FUNC; + + this.__dom_loaded_queue = []; + this.__request_queue = []; + this.__disabled_events = []; + this._flags = { + 'disable_all_events': false, + 'identify_called': false + }; + + // set up request queueing/batching + this.request_batchers = {}; + this._batch_requests = this.get_config('batch_requests'); + if (this._batch_requests) { + if (!_.localStorage.is_supported(true) || !USE_XHR) { + this._batch_requests = false; + console$1.log('Turning off Mixpanel request-queueing; needs XHR and localStorage support'); + } else { + this.init_batchers(); + if (sendBeacon && window$1.addEventListener) { + // Before page closes or hides (user tabs away etc), attempt to flush any events + // queued up via navigator.sendBeacon. Since sendBeacon doesn't report success/failure, + // events will not be removed from the persistent store; if the site is loaded again, + // the events will be flushed again on startup and deduplicated on the Mixpanel server + // side. + // There is no reliable way to capture only page close events, so we lean on the + // visibilitychange and pagehide events as recommended at + // https://developer.mozilla.org/en-US/docs/Web/API/Window/unload_event#usage_notes. + // These events fire when the user clicks away from the current page/tab, so will occur + // more frequently than page unload, but are the only mechanism currently for capturing + // this scenario somewhat reliably. + var flush_on_unload = _.bind(function() { + if (!this.request_batchers.events.stopped) { + this.request_batchers.events.flush({unloading: true}); + } + }, this); + window$1.addEventListener('pagehide', function(ev) { + if (ev['persisted']) { + flush_on_unload(); + } + }); + window$1.addEventListener('visibilitychange', function() { + if (document$1['visibilityState'] === 'hidden') { + flush_on_unload(); + } + }); + } + } + } + + this['persistence'] = this['cookie'] = new MixpanelPersistence(this['config']); + this.unpersisted_superprops = {}; + this._gdpr_init(); + + var uuid = _.UUID(); + if (!this.get_distinct_id()) { + // There is no need to set the distinct id + // or the device id if something was already stored + // in the persitence + this.register_once({ + 'distinct_id': DEVICE_ID_PREFIX + uuid, + '$device_id': uuid + }, ''); + } + + if (this.get_config('track_pageview')) { + this.track_pageview(); + } +}; + +// Private methods + +MixpanelLib.prototype._loaded = function() { + this.get_config('loaded')(this); + this._set_default_superprops(); +}; + +// update persistence with info on referrer, UTM params, etc +MixpanelLib.prototype._set_default_superprops = function() { + this['persistence'].update_search_keyword(document$1.referrer); + if (this.get_config('store_google')) { + this.register(_.info.campaignParams(), {persistent: false}); + } + if (this.get_config('save_referrer')) { + this['persistence'].update_referrer_info(document$1.referrer); + } +}; + +MixpanelLib.prototype._dom_loaded = function() { + _.each(this.__dom_loaded_queue, function(item) { + this._track_dom.apply(this, item); + }, this); + + if (!this.has_opted_out_tracking()) { + _.each(this.__request_queue, function(item) { + this._send_request.apply(this, item); + }, this); + } + + delete this.__dom_loaded_queue; + delete this.__request_queue; +}; + +MixpanelLib.prototype._track_dom = function(DomClass, args) { + if (this.get_config('img')) { + this.report_error('You can\'t use DOM tracking functions with img = true.'); + return false; + } + + if (!DOM_LOADED) { + this.__dom_loaded_queue.push([DomClass, args]); + return false; + } + + var dt = new DomClass().init(this); + return dt.track.apply(dt, args); +}; + +/** + * _prepare_callback() should be called by callers of _send_request for use + * as the callback argument. + * + * If there is no callback, this returns null. + * If we are going to make XHR/XDR requests, this returns a function. + * If we are going to use script tags, this returns a string to use as the + * callback GET param. + */ +MixpanelLib.prototype._prepare_callback = function(callback, data) { + if (_.isUndefined(callback)) { + return null; + } + + if (USE_XHR) { + var callback_function = function(response) { + callback(response, data); + }; + return callback_function; + } else { + // if the user gives us a callback, we store as a random + // property on this instances jsc function and update our + // callback string to reflect that. + var jsc = this['_jsc']; + var randomized_cb = '' + Math.floor(Math.random() * 100000000); + var callback_string = this.get_config('callback_fn') + '[' + randomized_cb + ']'; + jsc[randomized_cb] = function(response) { + delete jsc[randomized_cb]; + callback(response, data); + }; + return callback_string; + } +}; + +MixpanelLib.prototype._send_request = function(url, data, options, callback) { + var succeeded = true; + + if (ENQUEUE_REQUESTS) { + this.__request_queue.push(arguments); + return succeeded; + } + + var DEFAULT_OPTIONS = { + method: this.get_config('api_method'), + transport: this.get_config('api_transport'), + verbose: this.get_config('verbose') + }; + var body_data = null; + + if (!callback && (_.isFunction(options) || typeof options === 'string')) { + callback = options; + options = null; + } + options = _.extend(DEFAULT_OPTIONS, options || {}); + if (!USE_XHR) { + options.method = 'GET'; + } + var use_post = options.method === 'POST'; + var use_sendBeacon = sendBeacon && use_post && options.transport.toLowerCase() === 'sendbeacon'; + + // needed to correctly format responses + var verbose_mode = options.verbose; + if (data['verbose']) { verbose_mode = true; } + + if (this.get_config('test')) { data['test'] = 1; } + if (verbose_mode) { data['verbose'] = 1; } + if (this.get_config('img')) { data['img'] = 1; } + if (!USE_XHR) { + if (callback) { + data['callback'] = callback; + } else if (verbose_mode || this.get_config('test')) { + // Verbose output (from verbose mode, or an error in test mode) is a json blob, + // which by itself is not valid javascript. Without a callback, this verbose output will + // cause an error when returned via jsonp, so we force a no-op callback param. + // See the ECMA script spec: http://www.ecma-international.org/ecma-262/5.1/#sec-12.4 + data['callback'] = '(function(){})'; + } + } + + data['ip'] = this.get_config('ip')?1:0; + data['_'] = new Date().getTime().toString(); + + if (use_post) { + body_data = 'data=' + encodeURIComponent(data['data']); + delete data['data']; + } + + url += '?' + _.HTTPBuildQuery(data); + + var lib = this; + if ('img' in data) { + var img = document$1.createElement('img'); + img.src = url; + document$1.body.appendChild(img); + } else if (use_sendBeacon) { + try { + succeeded = sendBeacon(url, body_data); + } catch (e) { + lib.report_error(e); + succeeded = false; + } + try { + if (callback) { + callback(succeeded ? 1 : 0); + } + } catch (e) { + lib.report_error(e); + } + } else if (USE_XHR) { + try { + var req = new XMLHttpRequest(); + req.open(options.method, url, true); + + var headers = this.get_config('xhr_headers'); + if (use_post) { + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } + _.each(headers, function(headerValue, headerName) { + req.setRequestHeader(headerName, headerValue); + }); + + if (options.timeout_ms && typeof req.timeout !== 'undefined') { + req.timeout = options.timeout_ms; + var start_time = new Date().getTime(); + } + + // send the mp_optout cookie + // withCredentials cannot be modified until after calling .open on Android and Mobile Safari + req.withCredentials = true; + req.onreadystatechange = function () { + if (req.readyState === 4) { // XMLHttpRequest.DONE == 4, except in safari 4 + if (req.status === 200) { + if (callback) { + if (verbose_mode) { + var response; + try { + response = _.JSONDecode(req.responseText); + } catch (e) { + lib.report_error(e); + if (options.ignore_json_errors) { + response = req.responseText; + } else { + return; + } + } + callback(response); + } else { + callback(Number(req.responseText)); + } + } + } else { + var error; + if ( + req.timeout && + !req.status && + new Date().getTime() - start_time >= req.timeout + ) { + error = 'timeout'; + } else { + error = 'Bad HTTP status: ' + req.status + ' ' + req.statusText; + } + lib.report_error(error); + if (callback) { + if (verbose_mode) { + callback({status: 0, error: error, xhr_req: req}); + } else { + callback(0); + } + } + } + } + }; + req.send(body_data); + } catch (e) { + lib.report_error(e); + succeeded = false; + } + } else { + var script = document$1.createElement('script'); + script.type = 'text/javascript'; + script.async = true; + script.defer = true; + script.src = url; + var s = document$1.getElementsByTagName('script')[0]; + s.parentNode.insertBefore(script, s); + } + + return succeeded; +}; + +/** + * _execute_array() deals with processing any mixpanel function + * calls that were called before the Mixpanel library were loaded + * (and are thus stored in an array so they can be called later) + * + * Note: we fire off all the mixpanel function calls && user defined + * functions BEFORE we fire off mixpanel tracking calls. This is so + * identify/register/set_config calls can properly modify early + * tracking calls. + * + * @param {Array} array + */ +MixpanelLib.prototype._execute_array = function(array) { + var fn_name, alias_calls = [], other_calls = [], tracking_calls = []; + _.each(array, function(item) { + if (item) { + fn_name = item[0]; + if (_.isArray(fn_name)) { + tracking_calls.push(item); // chained call e.g. mixpanel.get_group().set() + } else if (typeof(item) === 'function') { + item.call(this); + } else if (_.isArray(item) && fn_name === 'alias') { + alias_calls.push(item); + } else if (_.isArray(item) && fn_name.indexOf('track') !== -1 && typeof(this[fn_name]) === 'function') { + tracking_calls.push(item); + } else { + other_calls.push(item); + } + } + }, this); + + var execute = function(calls, context) { + _.each(calls, function(item) { + if (_.isArray(item[0])) { + // chained call + var caller = context; + _.each(item, function(call) { + caller = caller[call[0]].apply(caller, call.slice(1)); + }); + } else { + this[item[0]].apply(this, item.slice(1)); + } + }, context); + }; + + execute(alias_calls, this); + execute(other_calls, this); + execute(tracking_calls, this); +}; + +// request queueing utils + +MixpanelLib.prototype.are_batchers_initialized = function() { + return !!this.request_batchers.events; +}; + +MixpanelLib.prototype.init_batchers = function() { + var token = this.get_config('token'); + if (!this.are_batchers_initialized()) { + var batcher_for = _.bind(function(attrs) { + return new RequestBatcher( + '__mpq_' + token + attrs.queue_suffix, + { + libConfig: this['config'], + sendRequestFunc: _.bind(function(data, options, cb) { + this._send_request( + this.get_config('api_host') + attrs.endpoint, + this._encode_data_for_request(data), + options, + this._prepare_callback(cb, data) + ); + }, this), + beforeSendHook: _.bind(function(item) { + return this._run_hook('before_send_' + attrs.type, item); + }, this), + errorReporter: this.get_config('error_reporter'), + stopAllBatchingFunc: _.bind(this.stop_batch_senders, this) + } + ); + }, this); + this.request_batchers = { + events: batcher_for({type: 'events', endpoint: '/track/', queue_suffix: '_ev'}), + people: batcher_for({type: 'people', endpoint: '/engage/', queue_suffix: '_pp'}), + groups: batcher_for({type: 'groups', endpoint: '/groups/', queue_suffix: '_gr'}) + }; + } + if (this.get_config('batch_autostart')) { + this.start_batch_senders(); + } +}; + +MixpanelLib.prototype.start_batch_senders = function() { + if (this.are_batchers_initialized()) { + this._batch_requests = true; + _.each(this.request_batchers, function(batcher) { + batcher.start(); + }); + } +}; + +MixpanelLib.prototype.stop_batch_senders = function() { + this._batch_requests = false; + _.each(this.request_batchers, function(batcher) { + batcher.stop(); + batcher.clear(); + }); +}; + +/** + * push() keeps the standard async-array-push + * behavior around after the lib is loaded. + * This is only useful for external integrations that + * do not wish to rely on our convenience methods + * (created in the snippet). + * + * ### Usage: + * mixpanel.push(['register', { a: 'b' }]); + * + * @param {Array} item A [function_name, args...] array to be executed + */ +MixpanelLib.prototype.push = function(item) { + this._execute_array([item]); +}; + +/** + * Disable events on the Mixpanel object. If passed no arguments, + * this function disables tracking of any event. If passed an + * array of event names, those events will be disabled, but other + * events will continue to be tracked. + * + * Note: this function does not stop other mixpanel functions from + * firing, such as register() or people.set(). + * + * @param {Array} [events] An array of event names to disable + */ +MixpanelLib.prototype.disable = function(events) { + if (typeof(events) === 'undefined') { + this._flags.disable_all_events = true; + } else { + this.__disabled_events = this.__disabled_events.concat(events); + } +}; + +MixpanelLib.prototype._encode_data_for_request = function(data) { + var encoded_data = _.JSONEncode(data); + if (this.get_config('api_payload_format') === PAYLOAD_TYPE_BASE64) { + encoded_data = _.base64Encode(encoded_data); + } + return {'data': encoded_data}; +}; + +// internal method for handling track vs batch-enqueue logic +MixpanelLib.prototype._track_or_batch = function(options, callback) { + var truncated_data = _.truncate(options.data, 255); + var endpoint = options.endpoint; + var batcher = options.batcher; + var should_send_immediately = options.should_send_immediately; + var send_request_options = options.send_request_options || {}; + callback = callback || NOOP_FUNC; + + var request_enqueued_or_initiated = true; + var send_request_immediately = _.bind(function() { + if (!send_request_options.skip_hooks) { + truncated_data = this._run_hook('before_send_' + options.type, truncated_data); + } + if (truncated_data) { + console$1.log('MIXPANEL REQUEST:'); + console$1.log(truncated_data); + return this._send_request( + endpoint, + this._encode_data_for_request(truncated_data), + send_request_options, + this._prepare_callback(callback, truncated_data) + ); + } else { + return null; + } + }, this); + + if (this._batch_requests && !should_send_immediately) { + batcher.enqueue(truncated_data, function(succeeded) { + if (succeeded) { + callback(1, truncated_data); + } else { + send_request_immediately(); + } + }); + } else { + request_enqueued_or_initiated = send_request_immediately(); + } + + return request_enqueued_or_initiated && truncated_data; +}; + +/** + * Track an event. This is the most important and + * frequently used Mixpanel function. + * + * ### Usage: + * + * // track an event named 'Registered' + * mixpanel.track('Registered', {'Gender': 'Male', 'Age': 21}); + * + * // track an event using navigator.sendBeacon + * mixpanel.track('Left page', {'duration_seconds': 35}, {transport: 'sendBeacon'}); + * + * To track link clicks or form submissions, see track_links() or track_forms(). + * + * @param {String} event_name The name of the event. This can be anything the user does - 'Button Click', 'Sign Up', 'Item Purchased', etc. + * @param {Object} [properties] A set of properties to include with the event you're sending. These describe the user who did the event or details about the event itself. + * @param {Object} [options] Optional configuration for this track request. + * @param {String} [options.transport] Transport method for network request ('xhr' or 'sendBeacon'). + * @param {Boolean} [options.send_immediately] Whether to bypass batching/queueing and send track request immediately. + * @param {Function} [callback] If provided, the callback function will be called after tracking the event. + * @returns {Boolean|Object} If the tracking request was successfully initiated/queued, an object + * with the tracking payload sent to the API server is returned; otherwise false. + */ +MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, properties, options, callback) { + if (!callback && typeof options === 'function') { + callback = options; + options = null; + } + options = options || {}; + var transport = options['transport']; // external API, don't minify 'transport' prop + if (transport) { + options.transport = transport; // 'transport' prop name can be minified internally + } + var should_send_immediately = options['send_immediately']; + if (typeof callback !== 'function') { + callback = NOOP_FUNC; + } + + if (_.isUndefined(event_name)) { + this.report_error('No event name provided to mixpanel.track'); + return; + } + + if (this._event_is_disabled(event_name)) { + callback(0); + return; + } + + // set defaults + properties = properties || {}; + properties['token'] = this.get_config('token'); + + // set $duration if time_event was previously called for this event + var start_timestamp = this['persistence'].remove_event_timer(event_name); + if (!_.isUndefined(start_timestamp)) { + var duration_in_ms = new Date().getTime() - start_timestamp; + properties['$duration'] = parseFloat((duration_in_ms / 1000).toFixed(3)); + } + + this._set_default_superprops(); + + var marketing_properties = this.get_config('track_marketing') + ? _.info.marketingParams() + : {}; + + // note: extend writes to the first object, so lets make sure we + // don't write to the persistence properties object and info + // properties object by passing in a new object + + // update properties with pageview info and super-properties + properties = _.extend( + {}, + _.info.properties(), + marketing_properties, + this['persistence'].properties(), + this.unpersisted_superprops, + properties + ); + + var property_blacklist = this.get_config('property_blacklist'); + if (_.isArray(property_blacklist)) { + _.each(property_blacklist, function(blacklisted_prop) { + delete properties[blacklisted_prop]; + }); + } else { + this.report_error('Invalid value for property_blacklist config: ' + property_blacklist); + } + + var data = { + 'event': event_name, + 'properties': properties + }; + var ret = this._track_or_batch({ + type: 'events', + data: data, + endpoint: this.get_config('api_host') + '/track/', + batcher: this.request_batchers.events, + should_send_immediately: should_send_immediately, + send_request_options: options + }, callback); + + return ret; +}); + +/** + * Register the current user into one/many groups. + * + * ### Usage: + * + * mixpanel.set_group('company', ['mixpanel', 'google']) // an array of IDs + * mixpanel.set_group('company', 'mixpanel') + * mixpanel.set_group('company', 128746312) + * + * @param {String} group_key Group key + * @param {Array|String|Number} group_ids An array of group IDs, or a singular group ID + * @param {Function} [callback] If provided, the callback will be called after tracking the event. + * + */ +MixpanelLib.prototype.set_group = addOptOutCheckMixpanelLib(function(group_key, group_ids, callback) { + if (!_.isArray(group_ids)) { + group_ids = [group_ids]; + } + var prop = {}; + prop[group_key] = group_ids; + this.register(prop); + return this['people'].set(group_key, group_ids, callback); +}); + +/** + * Add a new group for this user. + * + * ### Usage: + * + * mixpanel.add_group('company', 'mixpanel') + * + * @param {String} group_key Group key + * @param {*} group_id A valid Mixpanel property type + * @param {Function} [callback] If provided, the callback will be called after tracking the event. + */ +MixpanelLib.prototype.add_group = addOptOutCheckMixpanelLib(function(group_key, group_id, callback) { + var old_values = this.get_property(group_key); + if (old_values === undefined) { + var prop = {}; + prop[group_key] = [group_id]; + this.register(prop); + } else { + if (old_values.indexOf(group_id) === -1) { + old_values.push(group_id); + this.register(prop); + } + } + return this['people'].union(group_key, group_id, callback); +}); + +/** + * Remove a group from this user. + * + * ### Usage: + * + * mixpanel.remove_group('company', 'mixpanel') + * + * @param {String} group_key Group key + * @param {*} group_id A valid Mixpanel property type + * @param {Function} [callback] If provided, the callback will be called after tracking the event. + */ +MixpanelLib.prototype.remove_group = addOptOutCheckMixpanelLib(function(group_key, group_id, callback) { + var old_value = this.get_property(group_key); + // if the value doesn't exist, the persistent store is unchanged + if (old_value !== undefined) { + var idx = old_value.indexOf(group_id); + if (idx > -1) { + old_value.splice(idx, 1); + this.register({group_key: old_value}); + } + if (old_value.length === 0) { + this.unregister(group_key); + } + } + return this['people'].remove(group_key, group_id, callback); +}); + +/** + * Track an event with specific groups. + * + * ### Usage: + * + * mixpanel.track_with_groups('purchase', {'product': 'iphone'}, {'University': ['UCB', 'UCLA']}) + * + * @param {String} event_name The name of the event (see `mixpanel.track()`) + * @param {Object=} properties A set of properties to include with the event you're sending (see `mixpanel.track()`) + * @param {Object=} groups An object mapping group name keys to one or more values + * @param {Function} [callback] If provided, the callback will be called after tracking the event. + */ +MixpanelLib.prototype.track_with_groups = addOptOutCheckMixpanelLib(function(event_name, properties, groups, callback) { + var tracking_props = _.extend({}, properties || {}); + _.each(groups, function(v, k) { + if (v !== null && v !== undefined) { + tracking_props[k] = v; + } + }); + return this.track(event_name, tracking_props, callback); +}); + +MixpanelLib.prototype._create_map_key = function (group_key, group_id) { + return group_key + '_' + JSON.stringify(group_id); +}; + +MixpanelLib.prototype._remove_group_from_cache = function (group_key, group_id) { + delete this._cached_groups[this._create_map_key(group_key, group_id)]; +}; + +/** + * Look up reference to a Mixpanel group + * + * ### Usage: + * + * mixpanel.get_group(group_key, group_id) + * + * @param {String} group_key Group key + * @param {Object} group_id A valid Mixpanel property type + * @returns {Object} A MixpanelGroup identifier + */ +MixpanelLib.prototype.get_group = function (group_key, group_id) { + var map_key = this._create_map_key(group_key, group_id); + var group = this._cached_groups[map_key]; + if (group === undefined || group._group_key !== group_key || group._group_id !== group_id) { + group = new MixpanelGroup(); + group._init(this, group_key, group_id); + this._cached_groups[map_key] = group; + } + return group; +}; + +/** + * Track a default Mixpanel page view event, which includes extra default event properties to + * improve page view data. The `config.track_pageview` option for mixpanel.init() + * may be turned on for tracking page loads automatically. + * + * ### Usage + * + * // track a default $mp_web_page_view event + * mixpanel.track_pageview(); + * + * // track a page view event with additional event properties + * mixpanel.track_pageview({'ab_test_variant': 'card-layout-b'}); + * + * // example approach to track page views on different page types as event properties + * mixpanel.track_pageview({'page': 'pricing'}); + * mixpanel.track_pageview({'page': 'homepage'}); + * + * // UNCOMMON: Tracking a page view event with a custom event_name option. NOT expected to be used for + * // individual pages on the same site or product. Use cases for custom event_name may be page + * // views on different products or internal applications that are considered completely separate + * mixpanel.track_pageview({'page': 'customer-search'}, {'event_name': '[internal] Admin Page View'}); + * + * @param {Object} [properties] An optional set of additional properties to send with the page view event + * @param {Object} [options] Page view tracking options + * @param {String} [options.event_name] - Alternate name for the tracking event + * @returns {Boolean|Object} If the tracking request was successfully initiated/queued, an object + * with the tracking payload sent to the API server is returned; otherwise false. + */ +MixpanelLib.prototype.track_pageview = addOptOutCheckMixpanelLib(function(properties, options) { + if (typeof properties !== 'object') { + properties = {}; + } + options = options || {}; + var event_name = options['event_name'] || '$mp_web_page_view'; + + var default_page_properties = _.extend( + _.info.mpPageViewProperties(), + _.info.campaignParams(), + _.info.clickParams() + ); + + var event_properties = _.extend( + {}, + default_page_properties, + properties + ); + + return this.track(event_name, event_properties); +}); + +/** + * Track clicks on a set of document elements. Selector must be a + * valid query. Elements must exist on the page at the time track_links is called. + * + * ### Usage: + * + * // track click for link id #nav + * mixpanel.track_links('#nav', 'Clicked Nav Link'); + * + * ### Notes: + * + * This function will wait up to 300 ms for the Mixpanel + * servers to respond. If they have not responded by that time + * it will head to the link without ensuring that your event + * has been tracked. To configure this timeout please see the + * set_config() documentation below. + * + * If you pass a function in as the properties argument, the + * function will receive the DOMElement that triggered the + * event as an argument. You are expected to return an object + * from the function; any properties defined on this object + * will be sent to mixpanel as event properties. + * + * @type {Function} + * @param {Object|String} query A valid DOM query, element or jQuery-esque list + * @param {String} event_name The name of the event to track + * @param {Object|Function} [properties] A properties object or function that returns a dictionary of properties when passed a DOMElement + */ +MixpanelLib.prototype.track_links = function() { + return this._track_dom.call(this, LinkTracker, arguments); +}; + +/** + * Track form submissions. Selector must be a valid query. + * + * ### Usage: + * + * // track submission for form id 'register' + * mixpanel.track_forms('#register', 'Created Account'); + * + * ### Notes: + * + * This function will wait up to 300 ms for the mixpanel + * servers to respond, if they have not responded by that time + * it will head to the link without ensuring that your event + * has been tracked. To configure this timeout please see the + * set_config() documentation below. + * + * If you pass a function in as the properties argument, the + * function will receive the DOMElement that triggered the + * event as an argument. You are expected to return an object + * from the function; any properties defined on this object + * will be sent to mixpanel as event properties. + * + * @type {Function} + * @param {Object|String} query A valid DOM query, element or jQuery-esque list + * @param {String} event_name The name of the event to track + * @param {Object|Function} [properties] This can be a set of properties, or a function that returns a set of properties after being passed a DOMElement + */ +MixpanelLib.prototype.track_forms = function() { + return this._track_dom.call(this, FormTracker, arguments); +}; + +/** + * Time an event by including the time between this call and a + * later 'track' call for the same event in the properties sent + * with the event. + * + * ### Usage: + * + * // time an event named 'Registered' + * mixpanel.time_event('Registered'); + * mixpanel.track('Registered', {'Gender': 'Male', 'Age': 21}); + * + * When called for a particular event name, the next track call for that event + * name will include the elapsed time between the 'time_event' and 'track' + * calls. This value is stored as seconds in the '$duration' property. + * + * @param {String} event_name The name of the event. + */ +MixpanelLib.prototype.time_event = function(event_name) { + if (_.isUndefined(event_name)) { + this.report_error('No event name provided to mixpanel.time_event'); + return; + } + + if (this._event_is_disabled(event_name)) { + return; + } + + this['persistence'].set_event_timer(event_name, new Date().getTime()); +}; + +var REGISTER_DEFAULTS = { + 'persistent': true +}; +/** + * Helper to parse options param for register methods, maintaining + * legacy support for plain "days" param instead of options object + * @param {Number|Object} [days_or_options] 'days' option (Number), or Options object for register methods + * @returns {Object} options object + */ +var options_for_register = function(days_or_options) { + var options; + if (_.isObject(days_or_options)) { + options = days_or_options; + } else if (!_.isUndefined(days_or_options)) { + options = {'days': days_or_options}; + } else { + options = {}; + } + return _.extend({}, REGISTER_DEFAULTS, options); +}; + +/** + * Register a set of super properties, which are included with all + * events. This will overwrite previous super property values. + * + * ### Usage: + * + * // register 'Gender' as a super property + * mixpanel.register({'Gender': 'Female'}); + * + * // register several super properties when a user signs up + * mixpanel.register({ + * 'Email': 'jdoe@example.com', + * 'Account Type': 'Free' + * }); + * + * // register only for the current pageload + * mixpanel.register({'Name': 'Pat'}, {persistent: false}); + * + * @param {Object} properties An associative array of properties to store about the user + * @param {Number|Object} [days_or_options] Options object or number of days since the user's last visit to store the super properties (only valid for persisted props) + * @param {boolean} [days_or_options.days] - number of days since the user's last visit to store the super properties (only valid for persisted props) + * @param {boolean} [days_or_options.persistent=true] - whether to put in persistent storage (cookie/localStorage) + */ +MixpanelLib.prototype.register = function(props, days_or_options) { + var options = options_for_register(days_or_options); + if (options['persistent']) { + this['persistence'].register(props, options['days']); + } else { + _.extend(this.unpersisted_superprops, props); + } +}; + +/** + * Register a set of super properties only once. This will not + * overwrite previous super property values, unlike register(). + * + * ### Usage: + * + * // register a super property for the first time only + * mixpanel.register_once({ + * 'First Login Date': new Date().toISOString() + * }); + * + * // register once, only for the current pageload + * mixpanel.register_once({ + * 'First interaction time': new Date().toISOString() + * }, 'None', {persistent: false}); + * + * ### Notes: + * + * If default_value is specified, current super properties + * with that value will be overwritten. + * + * @param {Object} properties An associative array of properties to store about the user + * @param {*} [default_value] Value to override if already set in super properties (ex: 'False') Default: 'None' + * @param {Number|Object} [days_or_options] Options object or number of days since the user's last visit to store the super properties (only valid for persisted props) + * @param {boolean} [days_or_options.days] - number of days since the user's last visit to store the super properties (only valid for persisted props) + * @param {boolean} [days_or_options.persistent=true] - whether to put in persistent storage (cookie/localStorage) + */ +MixpanelLib.prototype.register_once = function(props, default_value, days_or_options) { + var options = options_for_register(days_or_options); + if (options['persistent']) { + this['persistence'].register_once(props, default_value, options['days']); + } else { + if (typeof(default_value) === 'undefined') { + default_value = 'None'; + } + _.each(props, function(val, prop) { + if (!this.unpersisted_superprops.hasOwnProperty(prop) || this.unpersisted_superprops[prop] === default_value) { + this.unpersisted_superprops[prop] = val; + } + }, this); + } +}; + +/** + * Delete a super property stored with the current user. + * + * @param {String} property The name of the super property to remove + * @param {Object} [options] + * @param {boolean} [options.persistent=true] - whether to look in persistent storage (cookie/localStorage) + */ +MixpanelLib.prototype.unregister = function(property, options) { + options = options_for_register(options); + if (options['persistent']) { + this['persistence'].unregister(property); + } else { + delete this.unpersisted_superprops[property]; + } +}; + +MixpanelLib.prototype._register_single = function(prop, value) { + var props = {}; + props[prop] = value; + this.register(props); +}; + +/** + * Identify a user with a unique ID to track user activity across + * devices, tie a user to their events, and create a user profile. + * If you never call this method, unique visitors are tracked using + * a UUID generated the first time they visit the site. + * + * Call identify when you know the identity of the current user, + * typically after login or signup. We recommend against using + * identify for anonymous visitors to your site. + * + * ### Notes: + * If your project has + * ID Merge + * enabled, the identify method will connect pre- and + * post-authentication events when appropriate. + * + * If your project does not have ID Merge enabled, identify will + * change the user's local distinct_id to the unique ID you pass. + * Events tracked prior to authentication will not be connected + * to the same user identity. If ID Merge is disabled, alias can + * be used to connect pre- and post-registration events. + * + * @param {String} [unique_id] A string that uniquely identifies a user. If not provided, the distinct_id currently in the persistent store (cookie or localStorage) will be used. + */ +MixpanelLib.prototype.identify = function( + new_distinct_id, _set_callback, _add_callback, _append_callback, _set_once_callback, _union_callback, _unset_callback, _remove_callback +) { + // Optional Parameters + // _set_callback:function A callback to be run if and when the People set queue is flushed + // _add_callback:function A callback to be run if and when the People add queue is flushed + // _append_callback:function A callback to be run if and when the People append queue is flushed + // _set_once_callback:function A callback to be run if and when the People set_once queue is flushed + // _union_callback:function A callback to be run if and when the People union queue is flushed + // _unset_callback:function A callback to be run if and when the People unset queue is flushed + + var previous_distinct_id = this.get_distinct_id(); + if (new_distinct_id && previous_distinct_id !== new_distinct_id) { + // we allow the following condition if previous distinct_id is same as new_distinct_id + // so that you can force flush people updates for anonymous profiles. + if (typeof new_distinct_id === 'string' && new_distinct_id.indexOf(DEVICE_ID_PREFIX) === 0) { + this.report_error('distinct_id cannot have $device: prefix'); + return -1; + } + this.register({'$user_id': new_distinct_id}); + } + + if (!this.get_property('$device_id')) { + // The persisted distinct id might not actually be a device id at all + // it might be a distinct id of the user from before + var device_id = previous_distinct_id; + this.register_once({ + '$had_persisted_distinct_id': true, + '$device_id': device_id + }, ''); + } + + // identify only changes the distinct id if it doesn't match either the existing or the alias; + // if it's new, blow away the alias as well. + if (new_distinct_id !== previous_distinct_id && new_distinct_id !== this.get_property(ALIAS_ID_KEY)) { + this.unregister(ALIAS_ID_KEY); + this.register({'distinct_id': new_distinct_id}); + } + this._flags.identify_called = true; + // Flush any queued up people requests + this['people']._flush(_set_callback, _add_callback, _append_callback, _set_once_callback, _union_callback, _unset_callback, _remove_callback); + + // send an $identify event any time the distinct_id is changing - logic on the server + // will determine whether or not to do anything with it. + if (new_distinct_id !== previous_distinct_id) { + this.track('$identify', { + 'distinct_id': new_distinct_id, + '$anon_distinct_id': previous_distinct_id + }, {skip_hooks: true}); + } +}; + +/** + * Clears super properties and generates a new random distinct_id for this instance. + * Useful for clearing data when a user logs out. + */ +MixpanelLib.prototype.reset = function() { + this['persistence'].clear(); + this._flags.identify_called = false; + var uuid = _.UUID(); + this.register_once({ + 'distinct_id': DEVICE_ID_PREFIX + uuid, + '$device_id': uuid + }, ''); +}; + +/** + * Returns the current distinct id of the user. This is either the id automatically + * generated by the library or the id that has been passed by a call to identify(). + * + * ### Notes: + * + * get_distinct_id() can only be called after the Mixpanel library has finished loading. + * init() has a loaded function available to handle this automatically. For example: + * + * // set distinct_id after the mixpanel library has loaded + * mixpanel.init('YOUR PROJECT TOKEN', { + * loaded: function(mixpanel) { + * distinct_id = mixpanel.get_distinct_id(); + * } + * }); + */ +MixpanelLib.prototype.get_distinct_id = function() { + return this.get_property('distinct_id'); +}; + +/** + * The alias method creates an alias which Mixpanel will use to + * remap one id to another. Multiple aliases can point to the + * same identifier. + * + * The following is a valid use of alias: + * + * mixpanel.alias('new_id', 'existing_id'); + * // You can add multiple id aliases to the existing ID + * mixpanel.alias('newer_id', 'existing_id'); + * + * Aliases can also be chained - the following is a valid example: + * + * mixpanel.alias('new_id', 'existing_id'); + * // chain newer_id - new_id - existing_id + * mixpanel.alias('newer_id', 'new_id'); + * + * Aliases cannot point to multiple identifiers - the following + * example will not work: + * + * mixpanel.alias('new_id', 'existing_id'); + * // this is invalid as 'new_id' already points to 'existing_id' + * mixpanel.alias('new_id', 'newer_id'); + * + * ### Notes: + * + * If your project does not have + * ID Merge + * enabled, the best practice is to call alias once when a unique + * ID is first created for a user (e.g., when a user first registers + * for an account). Do not use alias multiple times for a single + * user without ID Merge enabled. + * + * @param {String} alias A unique identifier that you want to use for this user in the future. + * @param {String} [original] The current identifier being used for this user. + */ +MixpanelLib.prototype.alias = function(alias, original) { + // If the $people_distinct_id key exists in persistence, there has been a previous + // mixpanel.people.identify() call made for this user. It is VERY BAD to make an alias with + // this ID, as it will duplicate users. + if (alias === this.get_property(PEOPLE_DISTINCT_ID_KEY)) { + this.report_error('Attempting to create alias for existing People user - aborting.'); + return -2; + } + + var _this = this; + if (_.isUndefined(original)) { + original = this.get_distinct_id(); + } + if (alias !== original) { + this._register_single(ALIAS_ID_KEY, alias); + return this.track('$create_alias', { + 'alias': alias, + 'distinct_id': original + }, { + skip_hooks: true + }, function() { + // Flush the people queue + _this.identify(alias); + }); + } else { + this.report_error('alias matches current distinct_id - skipping api call.'); + this.identify(alias); + return -1; + } +}; + +/** + * Provide a string to recognize the user by. The string passed to + * this method will appear in the Mixpanel Streams product rather + * than an automatically generated name. Name tags do not have to + * be unique. + * + * This value will only be included in Streams data. + * + * @param {String} name_tag A human readable name for the user + * @deprecated + */ +MixpanelLib.prototype.name_tag = function(name_tag) { + this._register_single('mp_name_tag', name_tag); +}; + +/** + * Update the configuration of a mixpanel library instance. + * + * The default config is: + * + * { + * // HTTP method for tracking requests + * api_method: 'POST' + * + * // transport for sending requests ('XHR' or 'sendBeacon') + * // NB: sendBeacon should only be used for scenarios such as + * // page unload where a "best-effort" attempt to send is + * // acceptable; the sendBeacon API does not support callbacks + * // or any way to know the result of the request. Mixpanel + * // tracking via sendBeacon will not support any event- + * // batching or retry mechanisms. + * api_transport: 'XHR' + * + * // request-batching/queueing/retry + * batch_requests: true, + * + * // maximum number of events/updates to send in a single + * // network request + * batch_size: 50, + * + * // milliseconds to wait between sending batch requests + * batch_flush_interval_ms: 5000, + * + * // milliseconds to wait for network responses to batch requests + * // before they are considered timed-out and retried + * batch_request_timeout_ms: 90000, + * + * // override value for cookie domain, only useful for ensuring + * // correct cross-subdomain cookies on unusual domains like + * // subdomain.mainsite.avocat.fr; NB this cannot be used to + * // set cookies on a different domain than the current origin + * cookie_domain: '' + * + * // super properties cookie expiration (in days) + * cookie_expiration: 365 + * + * // if true, cookie will be set with SameSite=None; Secure + * // this is only useful in special situations, like embedded + * // 3rd-party iframes that set up a Mixpanel instance + * cross_site_cookie: false + * + * // super properties span subdomains + * cross_subdomain_cookie: true + * + * // debug mode + * debug: false + * + * // if this is true, the mixpanel cookie or localStorage entry + * // will be deleted, and no user persistence will take place + * disable_persistence: false + * + * // if this is true, Mixpanel will automatically determine + * // City, Region and Country data using the IP address of + * //the client + * ip: true + * + * // opt users out of tracking by this Mixpanel instance by default + * opt_out_tracking_by_default: false + * + * // opt users out of browser data storage by this Mixpanel instance by default + * opt_out_persistence_by_default: false + * + * // persistence mechanism used by opt-in/opt-out methods - cookie + * // or localStorage - falls back to cookie if localStorage is unavailable + * opt_out_tracking_persistence_type: 'localStorage' + * + * // customize the name of cookie/localStorage set by opt-in/opt-out methods + * opt_out_tracking_cookie_prefix: null + * + * // type of persistent store for super properties (cookie/ + * // localStorage) if set to 'localStorage', any existing + * // mixpanel cookie value with the same persistence_name + * // will be transferred to localStorage and deleted + * persistence: 'cookie' + * + * // name for super properties persistent store + * persistence_name: '' + * + * // names of properties/superproperties which should never + * // be sent with track() calls + * property_blacklist: [] + * + * // if this is true, mixpanel cookies will be marked as + * // secure, meaning they will only be transmitted over https + * secure_cookie: false + * + * // disables enriching user profiles with first touch marketing data + * skip_first_touch_marketing: false + * + * // the amount of time track_links will + * // wait for Mixpanel's servers to respond + * track_links_timeout: 300 + * + * // adds any UTM parameters and click IDs present on the page to any events fired + * track_marketing: true + * + * // enables automatic page view tracking using default page view events through + * // the track_pageview() method + * track_pageview: false + * + * // if you set upgrade to be true, the library will check for + * // a cookie from our old js library and import super + * // properties from it, then the old cookie is deleted + * // The upgrade config option only works in the initialization, + * // so make sure you set it when you create the library. + * upgrade: false + * + * // extra HTTP request headers to set for each API request, in + * // the format {'Header-Name': value} + * xhr_headers: {} + * + * // whether to ignore or respect the web browser's Do Not Track setting + * ignore_dnt: false + * } + * + * + * @param {Object} config A dictionary of new configuration values to update + */ +MixpanelLib.prototype.set_config = function(config) { + if (_.isObject(config)) { + _.extend(this['config'], config); + + var new_batch_size = config['batch_size']; + if (new_batch_size) { + _.each(this.request_batchers, function(batcher) { + batcher.resetBatchSize(); + }); + } + + if (!this.get_config('persistence_name')) { + this['config']['persistence_name'] = this['config']['cookie_name']; + } + if (!this.get_config('disable_persistence')) { + this['config']['disable_persistence'] = this['config']['disable_cookie']; + } + + if (this['persistence']) { + this['persistence'].update_config(this['config']); + } + Config.DEBUG = Config.DEBUG || this.get_config('debug'); + } +}; + +/** + * returns the current config object for the library. + */ +MixpanelLib.prototype.get_config = function(prop_name) { + return this['config'][prop_name]; +}; + +/** + * Fetch a hook function from config, with safe default, and run it + * against the given arguments + * @param {string} hook_name which hook to retrieve + * @returns {any|null} return value of user-provided hook, or null if nothing was returned + */ +MixpanelLib.prototype._run_hook = function(hook_name) { + var ret = (this['config']['hooks'][hook_name] || IDENTITY_FUNC).apply(this, slice.call(arguments, 1)); + if (typeof ret === 'undefined') { + this.report_error(hook_name + ' hook did not return a value'); + ret = null; + } + return ret; +}; + +/** + * Returns the value of the super property named property_name. If no such + * property is set, get_property() will return the undefined value. + * + * ### Notes: + * + * get_property() can only be called after the Mixpanel library has finished loading. + * init() has a loaded function available to handle this automatically. For example: + * + * // grab value for 'user_id' after the mixpanel library has loaded + * mixpanel.init('YOUR PROJECT TOKEN', { + * loaded: function(mixpanel) { + * user_id = mixpanel.get_property('user_id'); + * } + * }); + * + * @param {String} property_name The name of the super property you want to retrieve + */ +MixpanelLib.prototype.get_property = function(property_name) { + return this['persistence']['props'][property_name]; +}; + +MixpanelLib.prototype.toString = function() { + var name = this.get_config('name'); + if (name !== PRIMARY_INSTANCE_NAME) { + name = PRIMARY_INSTANCE_NAME + '.' + name; + } + return name; +}; + +MixpanelLib.prototype._event_is_disabled = function(event_name) { + return _.isBlockedUA(userAgent) || + this._flags.disable_all_events || + _.include(this.__disabled_events, event_name); +}; + +// perform some housekeeping around GDPR opt-in/out state +MixpanelLib.prototype._gdpr_init = function() { + var is_localStorage_requested = this.get_config('opt_out_tracking_persistence_type') === 'localStorage'; + + // try to convert opt-in/out cookies to localStorage if possible + if (is_localStorage_requested && _.localStorage.is_supported()) { + if (!this.has_opted_in_tracking() && this.has_opted_in_tracking({'persistence_type': 'cookie'})) { + this.opt_in_tracking({'enable_persistence': false}); + } + if (!this.has_opted_out_tracking() && this.has_opted_out_tracking({'persistence_type': 'cookie'})) { + this.opt_out_tracking({'clear_persistence': false}); + } + this.clear_opt_in_out_tracking({ + 'persistence_type': 'cookie', + 'enable_persistence': false + }); + } + + // check whether the user has already opted out - if so, clear & disable persistence + if (this.has_opted_out_tracking()) { + this._gdpr_update_persistence({'clear_persistence': true}); + + // check whether we should opt out by default + // note: we don't clear persistence here by default since opt-out default state is often + // used as an initial state while GDPR information is being collected + } else if (!this.has_opted_in_tracking() && ( + this.get_config('opt_out_tracking_by_default') || _.cookie.get('mp_optout') + )) { + _.cookie.remove('mp_optout'); + this.opt_out_tracking({ + 'clear_persistence': this.get_config('opt_out_persistence_by_default') + }); + } +}; + +/** + * Enable or disable persistence based on options + * only enable/disable if persistence is not already in this state + * @param {boolean} [options.clear_persistence] If true, will delete all data stored by the sdk in persistence and disable it + * @param {boolean} [options.enable_persistence] If true, will re-enable sdk persistence + */ +MixpanelLib.prototype._gdpr_update_persistence = function(options) { + var disabled; + if (options && options['clear_persistence']) { + disabled = true; + } else if (options && options['enable_persistence']) { + disabled = false; + } else { + return; + } + + if (!this.get_config('disable_persistence') && this['persistence'].disabled !== disabled) { + this['persistence'].set_disabled(disabled); + } + + if (disabled) { + _.each(this.request_batchers, function(batcher) { + batcher.clear(); + }); + } +}; + +// call a base gdpr function after constructing the appropriate token and options args +MixpanelLib.prototype._gdpr_call_func = function(func, options) { + options = _.extend({ + 'track': _.bind(this.track, this), + 'persistence_type': this.get_config('opt_out_tracking_persistence_type'), + 'cookie_prefix': this.get_config('opt_out_tracking_cookie_prefix'), + 'cookie_expiration': this.get_config('cookie_expiration'), + 'cross_site_cookie': this.get_config('cross_site_cookie'), + 'cross_subdomain_cookie': this.get_config('cross_subdomain_cookie'), + 'cookie_domain': this.get_config('cookie_domain'), + 'secure_cookie': this.get_config('secure_cookie'), + 'ignore_dnt': this.get_config('ignore_dnt') + }, options); + + // check if localStorage can be used for recording opt out status, fall back to cookie if not + if (!_.localStorage.is_supported()) { + options['persistence_type'] = 'cookie'; + } + + return func(this.get_config('token'), { + track: options['track'], + trackEventName: options['track_event_name'], + trackProperties: options['track_properties'], + persistenceType: options['persistence_type'], + persistencePrefix: options['cookie_prefix'], + cookieDomain: options['cookie_domain'], + cookieExpiration: options['cookie_expiration'], + crossSiteCookie: options['cross_site_cookie'], + crossSubdomainCookie: options['cross_subdomain_cookie'], + secureCookie: options['secure_cookie'], + ignoreDnt: options['ignore_dnt'] + }); +}; + +/** + * Opt the user in to data tracking and cookies/localstorage for this Mixpanel instance + * + * ### Usage + * + * // opt user in + * mixpanel.opt_in_tracking(); + * + * // opt user in with specific event name, properties, cookie configuration + * mixpanel.opt_in_tracking({ + * track_event_name: 'User opted in', + * track_event_properties: { + * 'Email': 'jdoe@example.com' + * }, + * cookie_expiration: 30, + * secure_cookie: true + * }); + * + * @param {Object} [options] A dictionary of config options to override + * @param {function} [options.track] Function used for tracking a Mixpanel event to record the opt-in action (default is this Mixpanel instance's track method) + * @param {string} [options.track_event_name=$opt_in] Event name to be used for tracking the opt-in action + * @param {Object} [options.track_properties] Set of properties to be tracked along with the opt-in action + * @param {boolean} [options.enable_persistence=true] If true, will re-enable sdk persistence + * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable + * @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this Mixpanel instance's config) + * @param {string} [options.cookie_domain] Custom cookie domain (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.cross_site_cookie] Whether the opt-in cookie is set as cross-site-enabled (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this Mixpanel instance's config) + */ +MixpanelLib.prototype.opt_in_tracking = function(options) { + options = _.extend({ + 'enable_persistence': true + }, options); + + this._gdpr_call_func(optIn, options); + this._gdpr_update_persistence(options); +}; + +/** + * Opt the user out of data tracking and cookies/localstorage for this Mixpanel instance + * + * ### Usage + * + * // opt user out + * mixpanel.opt_out_tracking(); + * + * // opt user out with different cookie configuration from Mixpanel instance + * mixpanel.opt_out_tracking({ + * cookie_expiration: 30, + * secure_cookie: true + * }); + * + * @param {Object} [options] A dictionary of config options to override + * @param {boolean} [options.delete_user=true] If true, will delete the currently identified user's profile and clear all charges after opting the user out + * @param {boolean} [options.clear_persistence=true] If true, will delete all data stored by the sdk in persistence + * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable + * @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this Mixpanel instance's config) + * @param {string} [options.cookie_domain] Custom cookie domain (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.cross_site_cookie] Whether the opt-in cookie is set as cross-site-enabled (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this Mixpanel instance's config) + */ +MixpanelLib.prototype.opt_out_tracking = function(options) { + options = _.extend({ + 'clear_persistence': true, + 'delete_user': true + }, options); + + // delete user and clear charges since these methods may be disabled by opt-out + if (options['delete_user'] && this['people'] && this['people']._identify_called()) { + this['people'].delete_user(); + this['people'].clear_charges(); + } + + this._gdpr_call_func(optOut, options); + this._gdpr_update_persistence(options); +}; + +/** + * Check whether the user has opted in to data tracking and cookies/localstorage for this Mixpanel instance + * + * ### Usage + * + * var has_opted_in = mixpanel.has_opted_in_tracking(); + * // use has_opted_in value + * + * @param {Object} [options] A dictionary of config options to override + * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable + * @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name + * @returns {boolean} current opt-in status + */ +MixpanelLib.prototype.has_opted_in_tracking = function(options) { + return this._gdpr_call_func(hasOptedIn, options); +}; + +/** + * Check whether the user has opted out of data tracking and cookies/localstorage for this Mixpanel instance + * + * ### Usage + * + * var has_opted_out = mixpanel.has_opted_out_tracking(); + * // use has_opted_out value + * + * @param {Object} [options] A dictionary of config options to override + * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable + * @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name + * @returns {boolean} current opt-out status + */ +MixpanelLib.prototype.has_opted_out_tracking = function(options) { + return this._gdpr_call_func(hasOptedOut, options); +}; + +/** + * Clear the user's opt in/out status of data tracking and cookies/localstorage for this Mixpanel instance + * + * ### Usage + * + * // clear user's opt-in/out status + * mixpanel.clear_opt_in_out_tracking(); + * + * // clear user's opt-in/out status with specific cookie configuration - should match + * // configuration used when opt_in_tracking/opt_out_tracking methods were called. + * mixpanel.clear_opt_in_out_tracking({ + * cookie_expiration: 30, + * secure_cookie: true + * }); + * + * @param {Object} [options] A dictionary of config options to override + * @param {boolean} [options.enable_persistence=true] If true, will re-enable sdk persistence + * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable + * @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this Mixpanel instance's config) + * @param {string} [options.cookie_domain] Custom cookie domain (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.cross_site_cookie] Whether the opt-in cookie is set as cross-site-enabled (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this Mixpanel instance's config) + */ +MixpanelLib.prototype.clear_opt_in_out_tracking = function(options) { + options = _.extend({ + 'enable_persistence': true + }, options); + + this._gdpr_call_func(clearOptInOut, options); + this._gdpr_update_persistence(options); +}; + +MixpanelLib.prototype.report_error = function(msg, err) { + console$1.error.apply(console$1.error, arguments); + try { + if (!err && !(msg instanceof Error)) { + msg = new Error(msg); + } + this.get_config('error_reporter')(msg, err); + } catch(err) { + console$1.error(err); + } +}; + +// EXPORTS (for closure compiler) + +// MixpanelLib Exports +MixpanelLib.prototype['init'] = MixpanelLib.prototype.init; +MixpanelLib.prototype['reset'] = MixpanelLib.prototype.reset; +MixpanelLib.prototype['disable'] = MixpanelLib.prototype.disable; +MixpanelLib.prototype['time_event'] = MixpanelLib.prototype.time_event; +MixpanelLib.prototype['track'] = MixpanelLib.prototype.track; +MixpanelLib.prototype['track_links'] = MixpanelLib.prototype.track_links; +MixpanelLib.prototype['track_forms'] = MixpanelLib.prototype.track_forms; +MixpanelLib.prototype['track_pageview'] = MixpanelLib.prototype.track_pageview; +MixpanelLib.prototype['register'] = MixpanelLib.prototype.register; +MixpanelLib.prototype['register_once'] = MixpanelLib.prototype.register_once; +MixpanelLib.prototype['unregister'] = MixpanelLib.prototype.unregister; +MixpanelLib.prototype['identify'] = MixpanelLib.prototype.identify; +MixpanelLib.prototype['alias'] = MixpanelLib.prototype.alias; +MixpanelLib.prototype['name_tag'] = MixpanelLib.prototype.name_tag; +MixpanelLib.prototype['set_config'] = MixpanelLib.prototype.set_config; +MixpanelLib.prototype['get_config'] = MixpanelLib.prototype.get_config; +MixpanelLib.prototype['get_property'] = MixpanelLib.prototype.get_property; +MixpanelLib.prototype['get_distinct_id'] = MixpanelLib.prototype.get_distinct_id; +MixpanelLib.prototype['toString'] = MixpanelLib.prototype.toString; +MixpanelLib.prototype['opt_out_tracking'] = MixpanelLib.prototype.opt_out_tracking; +MixpanelLib.prototype['opt_in_tracking'] = MixpanelLib.prototype.opt_in_tracking; +MixpanelLib.prototype['has_opted_out_tracking'] = MixpanelLib.prototype.has_opted_out_tracking; +MixpanelLib.prototype['has_opted_in_tracking'] = MixpanelLib.prototype.has_opted_in_tracking; +MixpanelLib.prototype['clear_opt_in_out_tracking'] = MixpanelLib.prototype.clear_opt_in_out_tracking; +MixpanelLib.prototype['get_group'] = MixpanelLib.prototype.get_group; +MixpanelLib.prototype['set_group'] = MixpanelLib.prototype.set_group; +MixpanelLib.prototype['add_group'] = MixpanelLib.prototype.add_group; +MixpanelLib.prototype['remove_group'] = MixpanelLib.prototype.remove_group; +MixpanelLib.prototype['track_with_groups'] = MixpanelLib.prototype.track_with_groups; +MixpanelLib.prototype['start_batch_senders'] = MixpanelLib.prototype.start_batch_senders; +MixpanelLib.prototype['stop_batch_senders'] = MixpanelLib.prototype.stop_batch_senders; + +// MixpanelPersistence Exports +MixpanelPersistence.prototype['properties'] = MixpanelPersistence.prototype.properties; +MixpanelPersistence.prototype['update_search_keyword'] = MixpanelPersistence.prototype.update_search_keyword; +MixpanelPersistence.prototype['update_referrer_info'] = MixpanelPersistence.prototype.update_referrer_info; +MixpanelPersistence.prototype['get_cross_subdomain'] = MixpanelPersistence.prototype.get_cross_subdomain; +MixpanelPersistence.prototype['clear'] = MixpanelPersistence.prototype.clear; + + +var instances = {}; +var extend_mp = function() { + // add all the sub mixpanel instances + _.each(instances, function(instance, name) { + if (name !== PRIMARY_INSTANCE_NAME) { mixpanel_master[name] = instance; } + }); + + // add private functions as _ + mixpanel_master['_'] = _; +}; + +var override_mp_init_func = function() { + // we override the snippets init function to handle the case where a + // user initializes the mixpanel library after the script loads & runs + mixpanel_master['init'] = function(token, config, name) { + if (name) { + // initialize a sub library + if (!mixpanel_master[name]) { + mixpanel_master[name] = instances[name] = create_mplib(token, config, name); + mixpanel_master[name]._loaded(); + } + return mixpanel_master[name]; + } else { + var instance = mixpanel_master; + + if (instances[PRIMARY_INSTANCE_NAME]) { + // main mixpanel lib already initialized + instance = instances[PRIMARY_INSTANCE_NAME]; + } else if (token) { + // intialize the main mixpanel lib + instance = create_mplib(token, config, PRIMARY_INSTANCE_NAME); + instance._loaded(); + instances[PRIMARY_INSTANCE_NAME] = instance; + } + + mixpanel_master = instance; + if (init_type === INIT_SNIPPET) { + window$1[PRIMARY_INSTANCE_NAME] = mixpanel_master; + } + extend_mp(); + } + }; +}; + +var add_dom_loaded_handler = function() { + // Cross browser DOM Loaded support + function dom_loaded_handler() { + // function flag since we only want to execute this once + if (dom_loaded_handler.done) { return; } + dom_loaded_handler.done = true; + + DOM_LOADED = true; + ENQUEUE_REQUESTS = false; + + _.each(instances, function(inst) { + inst._dom_loaded(); + }); + } + + function do_scroll_check() { + try { + document$1.documentElement.doScroll('left'); + } catch(e) { + setTimeout(do_scroll_check, 1); + return; + } + + dom_loaded_handler(); + } + + if (document$1.addEventListener) { + if (document$1.readyState === 'complete') { + // safari 4 can fire the DOMContentLoaded event before loading all + // external JS (including this file). you will see some copypasta + // on the internet that checks for 'complete' and 'loaded', but + // 'loaded' is an IE thing + dom_loaded_handler(); + } else { + document$1.addEventListener('DOMContentLoaded', dom_loaded_handler, false); + } + } else if (document$1.attachEvent) { + // IE + document$1.attachEvent('onreadystatechange', dom_loaded_handler); + + // check to make sure we arn't in a frame + var toplevel = false; + try { + toplevel = window$1.frameElement === null; + } catch(e) { + // noop + } + + if (document$1.documentElement.doScroll && toplevel) { + do_scroll_check(); + } + } + + // fallback handler, always will work + _.register_event(window$1, 'load', dom_loaded_handler, true); +}; + +function init_as_module() { + init_type = INIT_MODULE; + mixpanel_master = new MixpanelLib(); + + override_mp_init_func(); + mixpanel_master['init'](); + add_dom_loaded_handler(); + + return mixpanel_master; +} + +var mixpanel = init_as_module(); + +var mixpanel_cjs = mixpanel; + +var mixpanel$1 = /*#__PURE__*/_mergeNamespaces({ + __proto__: null, + default: mixpanel_cjs +}, [mixpanel_cjs]); + +const VERSION$1 = packageInfo.version; +// Needed to avoid error in CJS builds on some bundlers. +const mixpanelLib = mixpanel_cjs || mixpanel$1; +let mixpanelInstance; +/** + * Enum of mixpanel events + * @hidden + */ +const MIXPANEL_EVENT = { + VISUAL_SDK_RENDER_START: 'visual-sdk-render-start', + VISUAL_SDK_CALLED_INIT: 'visual-sdk-called-init', + VISUAL_SDK_RENDER_COMPLETE: 'visual-sdk-render-complete', + VISUAL_SDK_RENDER_FAILED: 'visual-sdk-render-failed', + VISUAL_SDK_TRIGGER: 'visual-sdk-trigger', + VISUAL_SDK_ON: 'visual-sdk-on', + VISUAL_SDK_EMBED_CREATE: 'visual-sdk-embed-create'}; +let isMixpanelInitialized = false; +let eventQueue = []; +/** + * Pushes the event with its Property key-value map to mixpanel. + * @param eventId + * @param eventProps + */ +function uploadMixpanelEvent(eventId, eventProps = {}) { + if (!isMixpanelInitialized) { + eventQueue.push({ eventId, eventProps }); + return; + } + mixpanelInstance.track(eventId, eventProps); +} +/** + * + */ +function emptyQueue() { + if (!isMixpanelInitialized) { + return; + } + eventQueue.forEach((event) => { + uploadMixpanelEvent(event.eventId, event.eventProps); + }); + eventQueue = []; +} +/** + * + * @param sessionInfo + */ +function initMixpanel(sessionInfo) { + if (!sessionInfo || !sessionInfo.mixpanelToken) { + logger$3.error(ERROR_MESSAGE.MIXPANEL_TOKEN_NOT_FOUND); + return; + } + // On a public cluster the user is anonymous, so don't set the identify to + // userGUID + const isPublicCluster = !!sessionInfo.isPublicUser; + const token = sessionInfo.mixpanelToken; + try { + if (token) { + mixpanelInstance = mixpanelLib.init(token, undefined, 'tsEmbed'); + if (!isPublicCluster) { + mixpanelInstance.identify(sessionInfo.userGUID); + } + mixpanelInstance.register_once({ + clusterId: sessionInfo.clusterId, + clusterName: sessionInfo.clusterName, + releaseVersion: sessionInfo.releaseVersion, + hostAppUrl: window?.location?.host || '', + sdkVersion: VERSION$1, + }); + isMixpanelInitialized = true; + emptyQueue(); + } + } + catch (e) { + logger$3.error('Error initializing mixpanel', e); + } +} + +var eventemitter3 = createCommonjsModule(function (module) { + +var has = Object.prototype.hasOwnProperty + , prefix = '~'; + +/** + * Constructor to create a storage for our `EE` objects. + * An `Events` instance is a plain object whose properties are event names. + * + * @constructor + * @private + */ +function Events() {} + +// +// We try to not inherit from `Object.prototype`. In some engines creating an +// instance in this way is faster than calling `Object.create(null)` directly. +// If `Object.create(null)` is not supported we prefix the event names with a +// character to make sure that the built-in object properties are not +// overridden or used as an attack vector. +// +if (Object.create) { + Events.prototype = Object.create(null); + + // + // This hack is needed because the `__proto__` property is still inherited in + // some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5. + // + if (!new Events().__proto__) prefix = false; +} + +/** + * Representation of a single event listener. + * + * @param {Function} fn The listener function. + * @param {*} context The context to invoke the listener with. + * @param {Boolean} [once=false] Specify if the listener is a one-time listener. + * @constructor + * @private + */ +function EE(fn, context, once) { + this.fn = fn; + this.context = context; + this.once = once || false; +} + +/** + * Add a listener for a given event. + * + * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. + * @param {(String|Symbol)} event The event name. + * @param {Function} fn The listener function. + * @param {*} context The context to invoke the listener with. + * @param {Boolean} once Specify if the listener is a one-time listener. + * @returns {EventEmitter} + * @private + */ +function addListener(emitter, event, fn, context, once) { + if (typeof fn !== 'function') { + throw new TypeError('The listener must be a function'); + } + + var listener = new EE(fn, context || emitter, once) + , evt = prefix ? prefix + event : event; + + if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++; + else if (!emitter._events[evt].fn) emitter._events[evt].push(listener); + else emitter._events[evt] = [emitter._events[evt], listener]; + + return emitter; +} + +/** + * Clear event by name. + * + * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. + * @param {(String|Symbol)} evt The Event name. + * @private + */ +function clearEvent(emitter, evt) { + if (--emitter._eventsCount === 0) emitter._events = new Events(); + else delete emitter._events[evt]; +} + +/** + * Minimal `EventEmitter` interface that is molded against the Node.js + * `EventEmitter` interface. + * + * @constructor + * @public + */ +function EventEmitter() { + this._events = new Events(); + this._eventsCount = 0; +} + +/** + * Return an array listing the events for which the emitter has registered + * listeners. + * + * @returns {Array} + * @public + */ +EventEmitter.prototype.eventNames = function eventNames() { + var names = [] + , events + , name; + + if (this._eventsCount === 0) return names; + + for (name in (events = this._events)) { + if (has.call(events, name)) names.push(prefix ? name.slice(1) : name); + } + + if (Object.getOwnPropertySymbols) { + return names.concat(Object.getOwnPropertySymbols(events)); + } + + return names; +}; + +/** + * Return the listeners registered for a given event. + * + * @param {(String|Symbol)} event The event name. + * @returns {Array} The registered listeners. + * @public + */ +EventEmitter.prototype.listeners = function listeners(event) { + var evt = prefix ? prefix + event : event + , handlers = this._events[evt]; + + if (!handlers) return []; + if (handlers.fn) return [handlers.fn]; + + for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) { + ee[i] = handlers[i].fn; + } + + return ee; +}; + +/** + * Return the number of listeners listening to a given event. + * + * @param {(String|Symbol)} event The event name. + * @returns {Number} The number of listeners. + * @public + */ +EventEmitter.prototype.listenerCount = function listenerCount(event) { + var evt = prefix ? prefix + event : event + , listeners = this._events[evt]; + + if (!listeners) return 0; + if (listeners.fn) return 1; + return listeners.length; +}; + +/** + * Calls each of the listeners registered for a given event. + * + * @param {(String|Symbol)} event The event name. + * @returns {Boolean} `true` if the event had listeners, else `false`. + * @public + */ +EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { + var evt = prefix ? prefix + event : event; + + if (!this._events[evt]) return false; + + var listeners = this._events[evt] + , len = arguments.length + , args + , i; + + if (listeners.fn) { + if (listeners.once) this.removeListener(event, listeners.fn, undefined, true); + + switch (len) { + case 1: return listeners.fn.call(listeners.context), true; + case 2: return listeners.fn.call(listeners.context, a1), true; + case 3: return listeners.fn.call(listeners.context, a1, a2), true; + case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true; + case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true; + case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true; + } + + for (i = 1, args = new Array(len -1); i < len; i++) { + args[i - 1] = arguments[i]; + } + + listeners.fn.apply(listeners.context, args); + } else { + var length = listeners.length + , j; + + for (i = 0; i < length; i++) { + if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true); + + switch (len) { + case 1: listeners[i].fn.call(listeners[i].context); break; + case 2: listeners[i].fn.call(listeners[i].context, a1); break; + case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break; + case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break; + default: + if (!args) for (j = 1, args = new Array(len -1); j < len; j++) { + args[j - 1] = arguments[j]; + } + + listeners[i].fn.apply(listeners[i].context, args); + } + } + } + + return true; +}; + +/** + * Add a listener for a given event. + * + * @param {(String|Symbol)} event The event name. + * @param {Function} fn The listener function. + * @param {*} [context=this] The context to invoke the listener with. + * @returns {EventEmitter} `this`. + * @public + */ +EventEmitter.prototype.on = function on(event, fn, context) { + return addListener(this, event, fn, context, false); +}; + +/** + * Add a one-time listener for a given event. + * + * @param {(String|Symbol)} event The event name. + * @param {Function} fn The listener function. + * @param {*} [context=this] The context to invoke the listener with. + * @returns {EventEmitter} `this`. + * @public + */ +EventEmitter.prototype.once = function once(event, fn, context) { + return addListener(this, event, fn, context, true); +}; + +/** + * Remove the listeners of a given event. + * + * @param {(String|Symbol)} event The event name. + * @param {Function} fn Only remove the listeners that match this function. + * @param {*} context Only remove the listeners that have this context. + * @param {Boolean} once Only remove one-time listeners. + * @returns {EventEmitter} `this`. + * @public + */ +EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) { + var evt = prefix ? prefix + event : event; + + if (!this._events[evt]) return this; + if (!fn) { + clearEvent(this, evt); + return this; + } + + var listeners = this._events[evt]; + + if (listeners.fn) { + if ( + listeners.fn === fn && + (!once || listeners.once) && + (!context || listeners.context === context) + ) { + clearEvent(this, evt); + } + } else { + for (var i = 0, events = [], length = listeners.length; i < length; i++) { + if ( + listeners[i].fn !== fn || + (once && !listeners[i].once) || + (context && listeners[i].context !== context) + ) { + events.push(listeners[i]); + } + } + + // + // Reset the array, or remove it completely if we have no more listeners. + // + if (events.length) this._events[evt] = events.length === 1 ? events[0] : events; + else clearEvent(this, evt); + } + + return this; +}; + +/** + * Remove all listeners, or those of the specified event. + * + * @param {(String|Symbol)} [event] The event name. + * @returns {EventEmitter} `this`. + * @public + */ +EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) { + var evt; + + if (event) { + evt = prefix ? prefix + event : event; + if (this._events[evt]) clearEvent(this, evt); + } else { + this._events = new Events(); + this._eventsCount = 0; + } + + return this; +}; + +// +// Alias methods names because people roll like that. +// +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; +EventEmitter.prototype.addListener = EventEmitter.prototype.on; + +// +// Expose the prefix. +// +EventEmitter.prefixed = prefix; + +// +// Allow `EventEmitter` to be imported as module namespace. +// +EventEmitter.EventEmitter = EventEmitter; + +// +// Expose the module. +// +{ + module.exports = EventEmitter; +} +}); + +var ReportType; +(function (ReportType) { + ReportType["CSP_VIOLATION"] = "csp-violation"; + ReportType["DEPRECATION"] = "deprecation"; + ReportType["INTERVENTION"] = "intervention"; +})(ReportType || (ReportType = {})); +let globalObserver = null; +/** + * Register a global ReportingObserver to capture all unhandled errors + * @param overrideExisting boolean to override existing observer + * @returns ReportingObserver | null + */ +function registerReportingObserver(overrideExisting = false) { + if (!(window.ReportingObserver)) { + logger$3.warn(ERROR_MESSAGE.MISSING_REPORTING_OBSERVER); + return null; + } + if (overrideExisting) { + resetGlobalReportingObserver(); + } + if (globalObserver) { + return globalObserver; + } + const embedConfig = getEmbedConfig(); + globalObserver = new ReportingObserver((reports) => { + reports.forEach((report) => { + const { type, url, body } = report; + const reportBody = body; + const isThoughtSpotHost = url + && url.startsWith(embedConfig.thoughtSpotHost); + const isFrameHostError = type === ReportType.CSP_VIOLATION + && reportBody.effectiveDirective === 'frame-ancestors'; + if (isThoughtSpotHost && isFrameHostError) { + if (!embedConfig.suppressErrorAlerts) { + alert(ERROR_MESSAGE.CSP_VIOLATION_ALERT); + } + logger$3.error(ERROR_MESSAGE.CSP_FRAME_HOST_VIOLATION_LOG_MESSAGE); + } + }); + }, { buffered: true }); + globalObserver.observe(); + return globalObserver; +} +/** + * Resets the global ReportingObserver + */ +function resetGlobalReportingObserver() { + if (globalObserver) + globalObserver.disconnect(); + globalObserver = null; +} + +/** + * + * @param url + * @param options + */ +function tokenizedFailureLoggedFetch(url, options = {}) { + return tokenizedFetch(url, options).then(async (r) => { + if (!r.ok && r.type !== 'opaqueredirect' && r.type !== 'opaque') { + logger$3.error(`Failed to fetch ${url}`, await r.text?.()); + } + return r; + }); +} +/** + * Fetches the session info from the ThoughtSpot server. + * @param thoughtspotHost + * @returns {Promise} + * @example + * ```js + * const response = await sessionInfoService(); + * ``` + */ +async function fetchPreauthInfoService(thoughtspotHost) { + const sessionInfoPath = `${thoughtspotHost}${EndPoints.PREAUTH_INFO}`; + const handleError = (e) => { + const error = new Error(`Failed to fetch auth info: ${e.message || e.statusText}`); + error.status = e.status; // Attach the status code to the error object + throw error; + }; + try { + const response = await tokenizedFailureLoggedFetch(sessionInfoPath); + return response; + } + catch (e) { + handleError(e); + return null; + } +} +/** + * Fetches the session info from the ThoughtSpot server. + * @param thoughtspotHost + * @returns {Promise} + * @example + * ```js + * const response = await sessionInfoService(); + * ``` + */ +async function fetchSessionInfoService(thoughtspotHost) { + const sessionInfoPath = `${thoughtspotHost}${EndPoints.SESSION_INFO}`; + const response = await tokenizedFailureLoggedFetch(sessionInfoPath); + if (!response.ok) { + throw new Error(`Failed to fetch session info: ${response.statusText}`); + } + const data = await response.json(); + return data; +} +/** + * Is active service to check if the user is logged in. + * @param thoughtSpotHost + * @version SDK: 1.28.4 | ThoughtSpot: * + */ +async function isActiveService(thoughtSpotHost) { + const isActiveUrl = `${thoughtSpotHost}${EndPoints.IS_ACTIVE}`; + try { + const res = await tokenizedFetch(isActiveUrl, { + credentials: 'include', + }); + return res.ok; + } + catch (e) { + logger$3.warn(`Is Logged In Service failed : ${e.message}`); + } + return false; +} + +let sessionInfo = null; +let preauthInfo = null; +/** + * Processes the session info response and returns the session info object. + * @param preauthInfoResp {any} Response from the session info API. + * @returns {PreauthInfo} The session info object. + * @example ```js + * const preauthInfoResp = await fetch(sessionInfoPath); + * const sessionInfo = await formatPreauthInfo(preauthInfoResp); + * console.log(sessionInfo); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + */ +const formatPreauthInfo = async (preauthInfoResp) => { + try { + // Convert Headers to a plain object + const headers = {}; + preauthInfoResp?.headers?.forEach((value, key) => { + headers[key] = value; + }); + const data = await preauthInfoResp.json(); + return { + ...data, + status: 200, + headers, + }; + } + catch (error) { + return null; + } +}; +/** + * Returns the session info object and caches it for future use. + * Once fetched the session info object is cached and returned from the cache on + * subsequent calls. + * @example ```js + * const preauthInfo = await getPreauthInfo(); + * console.log(preauthInfo); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + * @returns {Promise} The session info object. + */ +async function getPreauthInfo(allowCache = true) { + if (!allowCache || !preauthInfo) { + try { + const host = getEmbedConfig().thoughtSpotHost; + const sessionResponse = await fetchPreauthInfoService(host); + const processedPreauthInfo = await formatPreauthInfo(sessionResponse); + preauthInfo = processedPreauthInfo; + } + catch (error) { + return null; + } + } + return preauthInfo; +} +/** + * Returns the cached session info object and caches it for future use. + * Once fetched the session info object is cached and returned from the cache on + * subsequent calls. + * This cache is cleared when inti is called OR resetCachedSessionInfo is called. + * @example ```js + * const sessionInfo = await getSessionInfo(); + * console.log(sessionInfo); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + * @returns {Promise} The session info object. + */ +async function getSessionInfo() { + if (!sessionInfo) { + const host = getEmbedConfig().thoughtSpotHost; + const sessionResponse = await fetchSessionInfoService(host); + const processedSessionInfo = getSessionDetails(sessionResponse); + sessionInfo = processedSessionInfo; + } + return sessionInfo; +} +/** + * Processes the session info response and returns the session info object. + * @param sessionInfoResp {any} Response from the session info API. + * @returns {SessionInfo} The session info object. + * @example ```js + * const sessionInfoResp = await fetch(sessionInfoPath); + * const sessionInfo = getSessionDetails(sessionInfoResp); + * console.log(sessionInfo); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + */ +const getSessionDetails = (sessionInfoResp) => { + const devMixpanelToken = sessionInfoResp.configInfo.mixpanelConfig.devSdkKey; + const prodMixpanelToken = sessionInfoResp.configInfo.mixpanelConfig.prodSdkKey; + const mixpanelToken = sessionInfoResp.configInfo.mixpanelConfig.production + ? prodMixpanelToken + : devMixpanelToken; + return { + userGUID: sessionInfoResp.userGUID, + mixpanelToken, + isPublicUser: sessionInfoResp.configInfo.isPublicUser, + releaseVersion: sessionInfoResp.releaseVersion, + clusterId: sessionInfoResp.configInfo.selfClusterId, + clusterName: sessionInfoResp.configInfo.selfClusterName, + ...sessionInfoResp, + }; +}; +/** + * Resets the cached session info object and forces a new fetch on the next call. + * @example ```js + * resetCachedSessionInfo(); + * const sessionInfo = await getSessionInfo(); + * console.log(sessionInfo); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + * @returns {void} + */ +function resetCachedSessionInfo() { + sessionInfo = null; +} +/** + * Resets the cached preauth info object and forces a new fetch on the next call. + * @example ```js + * resetCachedPreauthInfo(); + * const preauthInfo = await getPreauthInfo(); + * console.log(preauthInfo); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + * @returns {void} + */ +function resetCachedPreauthInfo() { + preauthInfo = null; +} + +/** + * This function resets all the services that are cached in the SDK. + * This is to be called when the user logs out of the application and also + * when init is called again. + * @version SDK: 1.30.2 | ThoughtSpot: * + */ +function resetAllCachedServices() { + resetCachedAuthToken(); + resetCachedSessionInfo(); + resetCachedPreauthInfo(); +} + +let loggedInStatus = false; +let samlAuthWindow = null; +let samlCompletionPromise = null; +let releaseVersion = ''; +const SSO_REDIRECTION_MARKER_GUID = '5e16222e-ef02-43e9-9fbd-24226bf3ce5b'; +/** + * Enum for auth failure types. + * This value is passed to the listener for {@link AuthStatus.FAILURE}. + * @group Authentication / Init + */ +var AuthFailureType; +(function (AuthFailureType) { + /** + * Authentication failed in the SDK authentication flow. + * + * Emitted when `init()` or auto-authentication cannot establish a logged-in session. + * For example, this can happen because of an invalid token, an auth request failure, + * or an auth promise rejection. + */ + AuthFailureType["SDK"] = "SDK"; + /** + * Browser cookie access is blocked for the embedded app. + * + * Emitted when the iframe reports that required cookies + * cannot be read or sent, commonly due to third-party cookie restrictions. + */ + AuthFailureType["NO_COOKIE_ACCESS"] = "NO_COOKIE_ACCESS"; + /** + * The current authentication token or session has expired. + * + * Emitted when the embed receives an auth-expiry signal and starts auth refresh + * handling. + */ + AuthFailureType["EXPIRY"] = "EXPIRY"; + /** + * A generic authentication failure that does not match a more specific type. + * + * Emitted as a fallback for app-reported auth failures in standard auth flows. + */ + AuthFailureType["OTHER"] = "OTHER"; + /** + * The user session timed out due to inactivity. + * + * Emitted when the app reports an idle-session timeout. + */ + AuthFailureType["IDLE_SESSION_TIMEOUT"] = "IDLE_SESSION_TIMEOUT"; + /** + * The app reports that the user is unauthenticated. + * + * Used primarily to classify unauthenticated failures in Embedded SSO flows. + */ + AuthFailureType["UNAUTHENTICATED_FAILURE"] = "UNAUTHENTICATED_FAILURE"; +})(AuthFailureType || (AuthFailureType = {})); +/** + * Enum for auth status emitted by the emitter returned from {@link init}. + * @group Authentication / Init + */ +var AuthStatus; +(function (AuthStatus) { + /** + * Emits when the SDK fails to authenticate. + */ + AuthStatus["FAILURE"] = "FAILURE"; + /** + * Emits when the SDK authentication step completes + * successfully (e.g., token exchange, cookie set). + * This fires before any iframe is rendered. Use + * this to know that auth passed and it is safe to + * proceed with rendering. The callback receives no + * arguments. + * @example + * ```js + * const authEE = init({ ... }); + * authEE.on(AuthStatus.SDK_SUCCESS, () => { + * // Auth done, iframe not loaded yet + * }); + * ``` + */ + AuthStatus["SDK_SUCCESS"] = "SDK_SUCCESS"; + /** + * @hidden + * Emits when iframe is loaded and session + * information is available. + */ + AuthStatus["SESSION_INFO_SUCCESS"] = "SESSION_INFO_SUCCESS"; + /** + * Emits when the ThoughtSpot app inside the + * embedded iframe confirms its session is active. + * This fires after the iframe loads and sends back an `AuthInit` event. + * @param sessionInfo Information about the user session, with details like `userGUID`. + * @see EmbedEvent.AuthInit + * @example + * ```js + * const authEE = init({ ... }); + * authEE.on(AuthStatus.SUCCESS, (sessionInfo) => { + * // App is loaded and authenticated + * console.log(sessionInfo.userGUID); + * }); + * ``` + */ + AuthStatus["SUCCESS"] = "SUCCESS"; + /** + * Emits when a user logs out + */ + AuthStatus["LOGOUT"] = "LOGOUT"; + /** + * Emitted when inPopup is true in the SAMLRedirect flow and the + * popup is waiting to be triggered either programmatically + * or by the trigger button. + * @version SDK: 1.19.0 + */ + AuthStatus["WAITING_FOR_POPUP"] = "WAITING_FOR_POPUP"; + /** + * Emitted when the SAML popup is closed without authentication + */ + AuthStatus["SAML_POPUP_CLOSED_NO_AUTH"] = "SAML_POPUP_CLOSED_NO_AUTH"; +})(AuthStatus || (AuthStatus = {})); +/** + * Events which can be triggered on the emitter returned from {@link init}. + * @group Authentication / Init + */ +var AuthEvent; +(function (AuthEvent) { + /** + * Manually trigger the SSO popup. This is useful when + * authStatus is SAMLRedirect/OIDCRedirect and inPopup is set to true + */ + AuthEvent["TRIGGER_SSO_POPUP"] = "TRIGGER_SSO_POPUP"; +})(AuthEvent || (AuthEvent = {})); +let authEE; +/** + * + * @param eventEmitter + */ +function setAuthEE(eventEmitter) { + authEE = eventEmitter; +} +/** + * + */ +function notifyAuthSDKSuccess() { + if (!authEE) { + logger$3.error(ERROR_MESSAGE.SDK_NOT_INITIALIZED); + return; + } + authEE.emit(AuthStatus.SDK_SUCCESS); +} +/** + * + */ +async function notifyAuthSuccess() { + if (!authEE) { + logger$3.error(ERROR_MESSAGE.SDK_NOT_INITIALIZED); + return; + } + try { + getPreauthInfo(); + const sessionInfo = await getSessionInfo(); + authEE.emit(AuthStatus.SUCCESS, sessionInfo); + } + catch (e) { + logger$3.error(ERROR_MESSAGE.SESSION_INFO_FAILED); + } +} +/** + * + * @param failureType + */ +function notifyAuthFailure(failureType) { + if (!authEE) { + logger$3.error(ERROR_MESSAGE.SDK_NOT_INITIALIZED); + return; + } + authEE.emit(AuthStatus.FAILURE, failureType); +} +/** + * + */ +function notifyLogout() { + if (!authEE) { + logger$3.error(ERROR_MESSAGE.SDK_NOT_INITIALIZED); + return; + } + authEE.emit(AuthStatus.LOGOUT); +} +/** + * Check if we are logged into the ThoughtSpot cluster + * @param thoughtSpotHost The ThoughtSpot cluster hostname or IP + */ +async function isLoggedIn(thoughtSpotHost) { + try { + const response = await isActiveService(thoughtSpotHost); + return response; + } + catch (e) { + return false; + } +} +/** + * Services to be called after the login is successful, + * This should be called after the cookie is set for cookie auth or + * after the token is set for cookieless. + * @return {Promise} + * @example + * ```js + * await postLoginService(); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + */ +async function postLoginService() { + try { + getPreauthInfo(); + const sessionInfo = await getSessionInfo(); + releaseVersion = sessionInfo.releaseVersion; + const embedConfig = getEmbedConfig(); + if (!embedConfig.disableSDKTracking) { + initMixpanel(sessionInfo); + } + } + catch (e) { + logger$3.error('Post login services failed.', e.message, e); + } +} +/** + * Return releaseVersion if available + */ +function getReleaseVersion() { + return releaseVersion; +} +/** + * Check if we are stuck at the SSO redirect URL + */ +function isAtSSORedirectUrl() { + return window.location.href.indexOf(getSSOMarker(SSO_REDIRECTION_MARKER_GUID)) >= 0; +} +/** + * Remove the SSO redirect URL marker + */ +function removeSSORedirectUrlMarker() { + // Note (sunny): This will leave a # around even if it was not in the URL + // to begin with. Trying to remove the hash by changing window.location will + // reload the page which we don't want. We'll live with adding an + // unnecessary hash to the parent page URL until we find any use case where + // that creates an issue. + // Replace any occurrences of ?ssoMarker=guid or &ssoMarker=guid. + let updatedHash = window.location.hash.replace(`?${getSSOMarker(SSO_REDIRECTION_MARKER_GUID)}`, ''); + updatedHash = updatedHash.replace(`&${getSSOMarker(SSO_REDIRECTION_MARKER_GUID)}`, ''); + window.location.hash = updatedHash; +} +/** + * Perform token based authentication + * @param embedConfig The embed configuration + */ +const doTokenAuth = async (embedConfig) => { + const { thoughtSpotHost, username, authEndpoint, getAuthToken, } = embedConfig; + if (!authEndpoint && !getAuthToken) { + throw new Error('Either auth endpoint or getAuthToken function must be provided'); + } + loggedInStatus = await isLoggedIn(thoughtSpotHost); + if (!loggedInStatus) { + let authToken; + try { + authToken = await getAuthenticationToken(embedConfig); + } + catch (e) { + loggedInStatus = false; + throw e; + } + let resp; + try { + resp = await fetchAuthPostService(thoughtSpotHost, username, authToken); + } + catch (e) { + resp = await fetchAuthService(thoughtSpotHost, username, authToken); + } + // token login issues a 302 when successful + loggedInStatus = resp.ok || resp.type === 'opaqueredirect'; + if (loggedInStatus && embedConfig.detectCookieAccessSlow) { + // When 3rd party cookie access is blocked, this will fail because + // cookies will not be sent with the call. + loggedInStatus = await isLoggedIn(thoughtSpotHost); + } + } + return loggedInStatus; +}; +/** + * Validate embedConfig parameters required for cookielessTokenAuth + * @param embedConfig The embed configuration + */ +const doCookielessTokenAuth = async (embedConfig) => { + const { authEndpoint, getAuthToken } = embedConfig; + if (!authEndpoint && !getAuthToken) { + throw new Error('Either auth endpoint or getAuthToken function must be provided'); + } + let authSuccess = false; + try { + const authToken = await getAuthenticationToken(embedConfig); + if (authToken) { + authSuccess = true; + } + } + catch { + authSuccess = false; + } + return authSuccess; +}; +/** + * Perform basic authentication to the ThoughtSpot cluster using the cluster + * credentials. + * + * Warning: This feature is primarily intended for developer testing. It is + * strongly advised not to use this authentication method in production. + * @param embedConfig The embed configuration + */ +const doBasicAuth = async (embedConfig) => { + const { thoughtSpotHost, username, password } = embedConfig; + const loggedIn = await isLoggedIn(thoughtSpotHost); + if (!loggedIn) { + const response = await fetchBasicAuthService(thoughtSpotHost, username, password); + loggedInStatus = response.ok; + if (embedConfig.detectCookieAccessSlow) { + loggedInStatus = await isLoggedIn(thoughtSpotHost); + } + } + else { + loggedInStatus = true; + } + return loggedInStatus; +}; +/** + * + * @param ssoURL + * @param triggerContainer + * @param triggerText + */ +async function samlPopupFlow(ssoURL, triggerContainer, triggerText) { + let popupClosedCheck; + const openPopup = () => { + if (samlAuthWindow === null || samlAuthWindow.closed) { + samlAuthWindow = window.open(ssoURL, '_blank', 'location=no,height=570,width=520,scrollbars=yes,status=yes'); + if (samlAuthWindow) { + popupClosedCheck = setInterval(() => { + if (samlAuthWindow.closed) { + clearInterval(popupClosedCheck); + if (samlCompletionPromise && !samlCompletionResolved) { + authEE?.emit(AuthStatus.SAML_POPUP_CLOSED_NO_AUTH); + } + } + }, 500); + } + } + else { + samlAuthWindow.focus(); + } + }; + let samlCompletionResolved = false; + authEE?.emit(AuthStatus.WAITING_FOR_POPUP); + const containerEl = getDOMNode(triggerContainer); + if (containerEl) { + containerEl.innerHTML = ''; + const authElem = document.getElementById('ts-auth-btn'); + authElem.textContent = triggerText; + authElem.addEventListener('click', openPopup, { once: true }); + } + samlCompletionPromise = samlCompletionPromise || new Promise((resolve, reject) => { + window.addEventListener('message', (e) => { + if (e.data.type === EmbedEvent.SAMLComplete) { + if (e.data.accessToken) { + const decodedToken = decodeURIComponent(e.data.accessToken); + storeAuthTokenInCache(decodedToken); + } + samlCompletionResolved = true; + if (popupClosedCheck) { + clearInterval(popupClosedCheck); + } + e.source.close(); + resolve(); + } + }); + }); + authEE?.once(AuthEvent.TRIGGER_SSO_POPUP, openPopup); + return samlCompletionPromise; +} +/** + * Perform SAML authentication + * @param embedConfig The embed configuration + * @param ssoEndPoint + */ +const doSSOAuth = async (embedConfig, ssoEndPoint) => { + const { thoughtSpotHost } = embedConfig; + const loggedIn = await isLoggedIn(thoughtSpotHost); + if (loggedIn) { + if (isAtSSORedirectUrl()) { + removeSSORedirectUrlMarker(); + } + loggedInStatus = true; + return; + } + // we have already tried authentication and it did not succeed, restore + // the current URL to the original one and invoke the callback. + if (isAtSSORedirectUrl()) { + removeSSORedirectUrlMarker(); + loggedInStatus = false; + return; + } + const ssoURL = `${thoughtSpotHost}${ssoEndPoint}`; + if (embedConfig.inPopup) { + await samlPopupFlow(ssoURL, embedConfig.authTriggerContainer, embedConfig.authTriggerText); + const cachedToken = getCacheAuthToken(); + if (cachedToken) { + loggedInStatus = true; + } + else { + loggedInStatus = await isLoggedIn(thoughtSpotHost); + } + return; + } + window.location.href = ssoURL; +}; +const doSamlAuth = async (embedConfig) => { + const { thoughtSpotHost } = embedConfig; + // redirect for SSO, when the SSO authentication is done, this page will be + // loaded again and the same JS will execute again. + const ssoRedirectUrl = embedConfig.inPopup + ? `${thoughtSpotHost}/v2/#/embed/saml-complete` + : getRedirectUrl(window.location.href, SSO_REDIRECTION_MARKER_GUID, embedConfig.redirectPath); + // bring back the page to the same URL + const ssoEndPoint = `${EndPoints.SAML_LOGIN_TEMPLATE(encodeURIComponent(ssoRedirectUrl))}`; + await doSSOAuth(embedConfig, ssoEndPoint); + return loggedInStatus; +}; +const doOIDCAuth = async (embedConfig) => { + const { thoughtSpotHost } = embedConfig; + // redirect for SSO, when the SSO authentication is done, this page will be + // loaded again and the same JS will execute again. + const ssoRedirectUrl = embedConfig.noRedirect || embedConfig.inPopup + ? `${thoughtSpotHost}/v2/#/embed/saml-complete` + : getRedirectUrl(window.location.href, SSO_REDIRECTION_MARKER_GUID, embedConfig.redirectPath); + // bring back the page to the same URL + const baseEndpoint = `${EndPoints.OIDC_LOGIN_TEMPLATE(encodeURIComponent(ssoRedirectUrl))}`; + const ssoEndPoint = `${baseEndpoint}${baseEndpoint.includes('?') ? '&' : '?'}forceSAMLAutoRedirect=true`; + await doSSOAuth(embedConfig, ssoEndPoint); + return loggedInStatus; +}; +/** + * Perform authentication on the ThoughtSpot cluster + * @param embedConfig The embed configuration + */ +const authenticate = async (embedConfig) => { + const { authType } = embedConfig; + switch (authType) { + case AuthType.SSO: + case AuthType.SAMLRedirect: + case AuthType.SAML: + return doSamlAuth(embedConfig); + case AuthType.OIDC: + case AuthType.OIDCRedirect: + return doOIDCAuth(embedConfig); + case AuthType.AuthServer: + case AuthType.TrustedAuthToken: + return doTokenAuth(embedConfig); + case AuthType.TrustedAuthTokenCookieless: + return doCookielessTokenAuth(embedConfig); + case AuthType.Basic: + return doBasicAuth(embedConfig); + default: + return Promise.resolve(true); + } +}; + +if (typeof Promise.withResolvers === 'undefined') { + Promise.withResolvers = () => { + let resolve; + let reject; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; + }; +} + +/** + * Reloads the ThoughtSpot iframe. + * @param iFrame + */ +const reload = (iFrame) => { + const src = iFrame.src; + iFrame.src = ''; + setTimeout(() => { + iFrame.src = src; + }, 100); +}; +/** + * Post iframe message. + * @param iFrame + * @param message + * @param message.type + * @param message.data + * @param message.context + * @param thoughtSpotHost + * @param channel + */ +function postIframeMessage(iFrame, message, thoughtSpotHost, channel) { + return iFrame.contentWindow?.postMessage(message, thoughtSpotHost, [channel?.port2]); +} +const TRIGGER_TIMEOUT = 30000; +/** + * + * @param iFrame + * @param messageType + * @param thoughtSpotHost + * @param data + * @param context + */ +function processTrigger(iFrame, messageType, thoughtSpotHost, data, context) { + return new Promise((res, rej) => { + if (messageType === HostEvent.Reload) { + reload(iFrame); + return res(null); + } + if (messageType === HostEvent.Present) { + const embedConfig = getEmbedConfig(); + const disableFullscreenPresentation = embedConfig?.disableFullscreenPresentation ?? true; + if (!disableFullscreenPresentation) { + handlePresentEvent(iFrame); + } + else { + logger$3.warn('Fullscreen presentation mode is disabled. Set disableFullscreenPresentation: false to enable this feature.'); + } + } + const channel = new MessageChannel(); + channel.port1.onmessage = ({ data: responseData }) => { + channel.port1.close(); + const error = responseData?.error || responseData?.data?.error; + if (error) { + rej(error); + } + else { + res(responseData); + } + }; + // Close the messageChannel and resolve the promise if timeout. + setTimeout(() => { + channel.port1.close(); + res(new Error(ERROR_MESSAGE.TRIGGER_TIMED_OUT)); + }, TRIGGER_TIMEOUT); + return postIframeMessage(iFrame, { type: messageType, data, context }, thoughtSpotHost, channel); + }); +} + +/** + * Copyright (c) 2022 + * + * Base classes + * @summary Base classes + * @author Ayon Ghosh + */ +const CONFIG_DEFAULTS = { + loginFailedMessage: 'Not logged in', + authTriggerText: 'Authorize', + authType: AuthType.None, + logLevel: LogLevel.ERROR, + waitForCleanupOnDestroy: false, + cleanupTimeout: 5000, +}; +let authPromise; +const getAuthPromise = () => authPromise; +/** + * Perform authentication on the ThoughtSpot app as applicable. + */ +const handleAuth = () => { + authPromise = authenticate(getEmbedConfig()); + authPromise.then((isLoggedIn) => { + if (!isLoggedIn) { + notifyAuthFailure(AuthFailureType.SDK); + } + else { + // Post login service is called after successful login. + postLoginService(); + notifyAuthSDKSuccess(); + } + }, () => { + notifyAuthFailure(AuthFailureType.SDK); + }); + return authPromise; +}; +const hostUrlToFeatureUrl = { + [PrefetchFeatures.SearchEmbed]: (url, flags) => `${url}v2/?${flags}#/embed/answer`, + [PrefetchFeatures.LiveboardEmbed]: (url, flags) => `${url}?${flags}`, + [PrefetchFeatures.FullApp]: (url, flags) => `${url}?${flags}`, + [PrefetchFeatures.VizEmbed]: (url, flags) => `${url}?${flags}`, +}; +/** + * Prefetches static resources from the specified URL. Web browsers can then cache the + * prefetched resources and serve them from the user's local disk to provide faster access + * to your app. + * @param url The URL provided for prefetch + * @param prefetchFeatures Specify features which needs to be prefetched. + * @param additionalFlags This can be used to add any URL flag. + * @version SDK: 1.4.0 | ThoughtSpot: ts7.sep.cl, 7.2.1 + * @group Global methods + */ +const prefetch = (url, prefetchFeatures, additionalFlags) => { + if (url === '') { + logger$3.warn('The prefetch method does not have a valid URL'); + } + else { + const features = [PrefetchFeatures.FullApp]; + let hostUrl = url || getEmbedConfig().thoughtSpotHost; + const prefetchFlags = { + [Param.EmbedApp]: true, + ...getEmbedConfig()?.additionalFlags, + ...additionalFlags, + }; + hostUrl = hostUrl[hostUrl.length - 1] === '/' ? hostUrl : `${hostUrl}/`; + Array.from(new Set(features + .map((feature) => hostUrlToFeatureUrl[feature](hostUrl, getQueryParamString(prefetchFlags))))) + .forEach((prefetchUrl, index) => { + const iFrame = document.createElement('iframe'); + iFrame.src = prefetchUrl; + iFrame.style.width = '0'; + iFrame.style.height = '0'; + iFrame.style.border = '0'; + // Make it 'fixed' to keep it in a different stacking + // context. This should solve the focus behaviours inside + // the iframe from interfering with main body. + iFrame.style.position = 'fixed'; + // Push it out of viewport. + iFrame.style.top = '100vh'; + iFrame.style.left = '100vw'; + iFrame.classList.add('prefetchIframe'); + iFrame.classList.add(`prefetchIframeNum-${index}`); + document.body.appendChild(iFrame); + }); + } +}; +/** + * + * @param embedConfig + */ +function sanity(embedConfig) { + if (embedConfig.thoughtSpotHost === undefined) { + throw new Error('ThoughtSpot host not provided'); + } + if (embedConfig.authType === AuthType.TrustedAuthToken) { + if (!embedConfig.authEndpoint && typeof embedConfig.getAuthToken !== 'function') { + throw new Error('Trusted auth should provide either authEndpoint or getAuthToken'); + } + } +} +/** + * + * @param embedConfig + */ +function backwardCompat(embedConfig) { + const newConfig = { ...embedConfig }; + if (embedConfig.noRedirect !== undefined && embedConfig.inPopup === undefined) { + newConfig.inPopup = embedConfig.noRedirect; + } + return newConfig; +} +const initFlagKey = 'initFlagKey'; +const createAndSetInitPromise = () => { + if (isWindowUndefined()) + return; + const { promise: initPromise, resolve: initPromiseResolve, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + } = Promise.withResolvers(); + const initFlagStore = { + initPromise, + isInitCalled: false, + isInitCompleted: false, + initPromiseResolve, + }; + storeValueInWindow(initFlagKey, initFlagStore, { + // In case of diff imports the promise might be already set + ignoreIfAlreadyExists: true, + }); + initPromise.finally(() => { + const curVal = getValueFromWindow(initFlagKey); + curVal.isInitCompleted = true; + storeValueInWindow(initFlagKey, curVal); + }); +}; +createAndSetInitPromise(); +const getInitPromise = () => getValueFromWindow(initFlagKey)?.initPromise; +const getIsInitCompleted = () => getValueFromWindow(initFlagKey)?.isInitCompleted; +const getIsInitCalled = () => !!getValueFromWindow(initFlagKey)?.isInitCalled; +/** + * Initializes the Visual Embed SDK globally and perform + * authentication if applicable. This function needs to be called before any ThoughtSpot + * component like Liveboard etc can be embedded. But need not wait for AuthEvent.SUCCESS + * to actually embed. That is handled internally. + * @param embedConfig The configuration object containing ThoughtSpot host, + * authentication mechanism and so on. + * @example + * ```js + * const authStatus = init({ + * thoughtSpotHost: 'https://my.thoughtspot.cloud', + * authType: AuthType.None, + * }); + * authStatus.on(AuthStatus.FAILURE, (reason) => { // do something here }); + * ``` + * @returns {@link AuthEventEmitter} event emitter which emits events on authentication success, + * failure and logout. See {@link AuthStatus} + * @version SDK: 1.0.0 | ThoughtSpot ts7.april.cl, 7.2.1 + * @group Authentication / Init + */ +const init = (embedConfig) => { + if (isWindowUndefined()) + return null; + sanity(embedConfig); + resetAllCachedServices(); + embedConfig = setEmbedConfig(backwardCompat({ + ...CONFIG_DEFAULTS, + ...embedConfig, + thoughtSpotHost: getThoughtSpotHost(embedConfig), + })); + setGlobalLogLevelOverride(embedConfig.logLevel); + registerReportingObserver(); + const authEE = new eventemitter3(); + setAuthEE(authEE); + handleAuth(); + const { password, ...configToTrack } = getEmbedConfig(); + uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_CALLED_INIT, { + ...configToTrack, + usedCustomizationSheet: embedConfig.customizations?.style?.customCSSUrl != null, + usedCustomizationVariables: embedConfig.customizations?.style?.customCSS?.variables != null, + usedCustomizationRules: embedConfig.customizations?.style?.customCSS?.rules_UNSTABLE != null, + usedCustomizationStrings: !!embedConfig.customizations?.content?.strings, + usedCustomizationIconSprite: !!embedConfig.customizations?.iconSpriteUrl, + }); + if (getEmbedConfig().callPrefetch) { + prefetch(getEmbedConfig().thoughtSpotHost); + } + // Resolves the promise created in the initPromiseKey + getValueFromWindow(initFlagKey).initPromiseResolve(authEE); + getValueFromWindow(initFlagKey).isInitCalled = true; + return authEE; +}; +/** + * + */ +function disableAutoLogin() { + getEmbedConfig().autoLogin = false; +} +let renderQueue = Promise.resolve(); +/** + * Renders functions in a queue, resolves to next function only after the callback next + * is called + * @param fn The function being registered + */ +const renderInQueue = (fn) => { + const { queueMultiRenders = false } = getEmbedConfig(); + if (queueMultiRenders) { + renderQueue = renderQueue.then(() => new Promise((res) => fn(res))); + return renderQueue; + } + // Sending an empty function to keep it consistent with the above usage. + return fn(() => { }); +}; + +/** + * Process the ExitPresentMode event and handle default fullscreen exit + * @param e - The event data + */ +function processExitPresentMode(e) { + const embedConfig = getEmbedConfig(); + const disableFullscreenPresentation = embedConfig?.disableFullscreenPresentation ?? true; + if (!disableFullscreenPresentation) { + handleExitPresentMode(); + } +} +/** + * Clears the cached preauth and session info. + */ +function processClearInfoCache() { + resetCachedPreauthInfo(); + resetCachedSessionInfo(); +} +/** + * + * @param e + * @param thoughtSpotHost + */ +function processCustomAction(e, thoughtSpotHost) { + const { session, embedAnswerData, contextMenuPoints } = e.data; + const answerService = new AnswerService(session, embedAnswerData || {}, thoughtSpotHost, contextMenuPoints?.selectedPoints); + return { + ...e, + answerService, + }; +} +/** + * Responds to AuthInit sent from host signifying successful authentication in host. + * @param e + * @returns {any} + */ +function processAuthInit(e) { + notifyAuthSuccess(); + // Expose only allowed details (eg: userGUID) back to SDK users. + return { + ...e, + data: { + userGUID: e.data?.userGUID || e.payload?.userGUID, + }, + }; +} +/** + * + * @param e + * @param containerEl + */ +function processNoCookieAccess(e, containerEl) { + const { loginFailedMessage, suppressNoCookieAccessAlert, ignoreNoCookieAccess, suppressErrorAlerts, } = getEmbedConfig(); + if (!ignoreNoCookieAccess) { + if (!suppressNoCookieAccessAlert && !suppressErrorAlerts) { + alert(ERROR_MESSAGE.THIRD_PARTY_COOKIE_BLOCKED_ALERT); + } + containerEl.innerHTML = loginFailedMessage; + } + notifyAuthFailure(AuthFailureType.NO_COOKIE_ACCESS); + return e; +} +/** + * + * @param e + * @param containerEl + */ +function processAuthFailure(e, containerEl) { + const { loginFailedMessage, authType, disableLoginFailurePage, autoLogin, } = getEmbedConfig(); + const isEmbeddedSSO = authType === AuthType.EmbeddedSSO; + const isTrustedAuth = authType === AuthType.TrustedAuthToken || authType === AuthType.TrustedAuthTokenCookieless; + const isEmbeddedSSOInfoFailure = isEmbeddedSSO && e?.data?.type === AuthFailureType.UNAUTHENTICATED_FAILURE; + if (autoLogin && isTrustedAuth) { + containerEl.innerHTML = loginFailedMessage; + notifyAuthFailure(AuthFailureType.IDLE_SESSION_TIMEOUT); + } + else if (authType !== AuthType.None && !disableLoginFailurePage && !isEmbeddedSSOInfoFailure) { + containerEl.innerHTML = loginFailedMessage; + notifyAuthFailure(AuthFailureType.OTHER); + } + resetCachedAuthToken(); + return e; +} +/** + * + * @param e + * @param containerEl + */ +function processAuthLogout(e, containerEl) { + const { loginFailedMessage } = getEmbedConfig(); + containerEl.innerHTML = loginFailedMessage; + resetCachedAuthToken(); + disableAutoLogin(); + notifyLogout(); + return e; +} +/** + * + * @param type + * @param e + * @param thoughtSpotHost + * @param containerEl + */ +function processEventData(type, eventData, thoughtSpotHost, containerEl) { + switch (type) { + case EmbedEvent.CustomAction: + return processCustomAction(eventData, thoughtSpotHost); + case EmbedEvent.AuthInit: + return processAuthInit(eventData); + case EmbedEvent.NoCookieAccess: + return processNoCookieAccess(eventData, containerEl); + case EmbedEvent.AuthFailure: + return processAuthFailure(eventData, containerEl); + case EmbedEvent.AuthLogout: + return processAuthLogout(eventData, containerEl); + case EmbedEvent.ExitPresentMode: + return processExitPresentMode(); + case EmbedEvent.CLEAR_INFO_CACHE: + return processClearInfoCache(); + } + return eventData; +} + +function isValidUpdateFiltersPayload(payload) { + if (!payload) + return false; + const isValidFilter = (f) => { + const hasColumn = typeof f.column === 'string' || typeof f.columnName === 'string'; + const hasOperator = typeof f.oper === 'string' || typeof f.operator === 'string'; + const hasValues = Array.isArray(f.values); + const validType = !f.type || typeof f.type === 'string'; + return hasColumn && hasOperator && hasValues && validType; + }; + const hasValidFilter = payload.filter && isValidFilter(payload.filter); + const hasValidFilters = Array.isArray(payload.filters) && payload.filters.length > 0 && payload.filters.every(isValidFilter); + return !!(hasValidFilter || hasValidFilters); +} +function isValidDrillDownPayload(payload) { + if (!payload) + return false; + const points = payload.points; + if (!points || typeof points !== 'object') + return false; + const hasClickedPoint = 'clickedPoint' in points && points.clickedPoint != null; + const hasSelectedPoints = Array.isArray(points.selectedPoints) && points.selectedPoints.length > 0; + return hasClickedPoint || hasSelectedPoints; +} +function createValidationError(message) { + const err = new Error(message); + err.isValidationError = true; + err.embedErrorDetails = { + type: EmbedEvent.Error, + data: { + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message, + code: EmbedErrorCodes.HOST_EVENT_VALIDATION, + error: message + }, + status: embedEventStatus.END + }; + throw err; +} +function throwUpdateFiltersValidationError() { + createValidationError(ERROR_MESSAGE.UPDATEFILTERS_INVALID_PAYLOAD); +} +function throwDrillDownValidationError() { + createValidationError(ERROR_MESSAGE.DRILLDOWN_INVALID_PAYLOAD); +} + +/** + * Maps HostEvent to its corresponding UIPassthroughEvent. + * Includes both custom-handler events (Pin, SaveAnswer, UpdateFilters, DrillDown) + * and getter events (GetAnswerSession, GetFilters, etc.) that use getDataWithPassthroughFallback. + */ +const PASSTHROUGH_MAP = { + // Custom handlers (setters with special logic) + [HostEvent.Pin]: UIPassthroughEvent.PinAnswerToLiveboard, + [HostEvent.SaveAnswer]: UIPassthroughEvent.SaveAnswer, + [HostEvent.UpdateFilters]: UIPassthroughEvent.UpdateFilters, + [HostEvent.DrillDown]: UIPassthroughEvent.Drilldown, + // Getters (use getDataWithPassthroughFallback) + [HostEvent.GetAnswerSession]: UIPassthroughEvent.GetAnswerSession, + [HostEvent.GetFilters]: UIPassthroughEvent.GetFilters, + [HostEvent.GetIframeUrl]: UIPassthroughEvent.GetIframeUrl, + [HostEvent.GetParameters]: UIPassthroughEvent.GetParameters, + [HostEvent.GetTML]: UIPassthroughEvent.GetTML, + [HostEvent.GetTabs]: UIPassthroughEvent.GetTabs, + [HostEvent.getExportRequestForCurrentPinboard]: UIPassthroughEvent.GetExportRequestForCurrentPinboard, +}; +class HostEventClient { + constructor(iFrame) { + /** Cached list of available UI passthrough keys from the embedded app */ + this.availablePassthroughKeysCache = null; + this.iFrame = iFrame; + this.customHandlers = { + [HostEvent.Pin]: (p, c) => this.handlePinEvent(p, c), + [HostEvent.SaveAnswer]: (p, c) => this.handleSaveAnswerEvent(p, c), + [HostEvent.UpdateFilters]: (p, c) => this.handleUpdateFiltersEvent(p, c), + [HostEvent.DrillDown]: (p, c) => this.handleDrillDownEvent(p, c), + }; + } + /** + * A wrapper over process trigger to + * @param {HostEvent} message Host event to send + * @param {any} data Data to send with the host event + * @returns {Promise} - the response from the process trigger + */ + async processTrigger(message, data, context) { + if (!this.iFrame) { + throw new Error('Iframe element is not set'); + } + const thoughtspotHost = getEmbedConfig().thoughtSpotHost; + return processTrigger(this.iFrame, message, thoughtspotHost, data, context); + } + async handleHostEventWithParam(apiName, parameters, context) { + const response = (await this.triggerUIPassthroughApi(apiName, parameters, context)) + ?.find?.((r) => r.error || r.value); + if (!response) { + const error = `No answer found${parameters.vizId ? ` for vizId: ${parameters.vizId}` : ''}.`; + throw { error }; + } + const errors = response.error + || response.value?.errors + || response.value?.error; + if (errors) { + const message = typeof errors === 'string' ? errors : JSON.stringify(errors); + throw { error: message }; + } + return { ...response.value }; + } + async hostEventFallback(hostEvent, data, context) { + return this.processTrigger(hostEvent, data, context); + } + /** + * For getter events that return data. Tries UI passthrough first; + * if the app doesn't support it (no response data), falls back to + * the legacy host event channel. Real errors are thrown as-is. + */ + async getDataWithPassthroughFallback(passthroughEvent, hostEvent, payload, context) { + const response = await this.triggerUIPassthroughApi(passthroughEvent, payload || {}, context); + const matched = response?.find?.((r) => r.error || r.value); + if (!matched) { + return this.hostEventFallback(hostEvent, payload, context); + } + const errors = matched.error + || matched.value?.errors + || matched.value?.error; + if (errors) { + const message = typeof errors === 'string' ? errors : JSON.stringify(errors); + throw new Error(message); + } + return { ...matched.value }; + } + /** + * Setter for the iframe element used for host events + * @param {HTMLIFrameElement} iFrame - the iframe element to set + */ + setIframeElement(iFrame) { + this.iFrame = iFrame; + } + /** + * Fetches the list of available UI passthrough keys from the embedded app. + * Result is cached for the session. Returns empty array on failure. + */ + async getAvailableUIPassthroughKeys(context) { + if (this.availablePassthroughKeysCache !== null) { + return this.availablePassthroughKeysCache; + } + try { + const response = await this.triggerUIPassthroughApi(UIPassthroughEvent.GetAvailableUIPassthroughs, {}, context); + const matched = response?.find?.((r) => r.value && !r.error); + const keys = matched?.value?.keys; + this.availablePassthroughKeysCache = Array.isArray(keys) ? keys : []; + return this.availablePassthroughKeysCache; + } + catch { + return []; + } + } + async triggerUIPassthroughApi(apiName, parameters, context) { + const res = await this.processTrigger(HostEvent.UIPassthrough, { + type: apiName, + parameters, + }, context); + return res; + } + async handlePinEvent(payload, context) { + if (!payload || !('newVizName' in payload)) { + return this.hostEventFallback(HostEvent.Pin, payload, context); + } + const formattedPayload = { + ...payload, + pinboardId: payload.liveboardId ?? payload.pinboardId, + newPinboardName: payload.newLiveboardName ?? payload.newPinboardName, + }; + const data = await this.handleHostEventWithParam(UIPassthroughEvent.PinAnswerToLiveboard, formattedPayload, context); + return { + ...data, + liveboardId: data.pinboardId, + }; + } + async handleSaveAnswerEvent(payload, context) { + if (!payload || !('name' in payload) || !('description' in payload)) { + // Save is the fallback for SaveAnswer + return this.hostEventFallback(HostEvent.Save, payload, context); + } + const data = await this.handleHostEventWithParam(UIPassthroughEvent.SaveAnswer, payload, context); + return { + ...data, + answerId: data?.saveResponse?.data?.Answer__save?.answer?.id, + }; + } + handleUpdateFiltersEvent(payload, context) { + if (!isValidUpdateFiltersPayload(payload)) { + throwUpdateFiltersValidationError(); + } + return this.handleHostEventWithParam(UIPassthroughEvent.UpdateFilters, payload, context); + } + handleDrillDownEvent(payload, context) { + if (!isValidDrillDownPayload(payload)) { + throwDrillDownValidationError(); + } + return this.handleHostEventWithParam(UIPassthroughEvent.Drilldown, payload, context); + } + /** + * Dispatches a host event using the appropriate channel: + * 1. If the embedded app supports UI passthrough for this event, use it (custom handler or getter). + * 2. Otherwise fall back to the legacy host event channel. + * + * @param hostEvent - The host event to trigger + * @param payload - Optional payload for the event + * @param context - Optional context (e.g. vizId) for scoped operations + */ + async triggerHostEvent(hostEvent, payload, context) { + const customHandler = this.customHandlers[hostEvent]; + const passthroughEvent = PASSTHROUGH_MAP[hostEvent]; + // If embedded app supports passthrough but not this event, use legacy channel + const keys = passthroughEvent ? await this.getAvailableUIPassthroughKeys(context) : []; + if (passthroughEvent && keys.length > 0 && !keys.includes(passthroughEvent)) { + return this.hostEventFallback(hostEvent, payload, context); + } + // Custom handler (setters) > getter passthrough > legacy fallback + return (customHandler + ? customHandler(payload, context) + : passthroughEvent + ? this.getDataWithPassthroughFallback(passthroughEvent, hostEvent, payload, context) + : this.hostEventFallback(hostEvent, payload, context)); + } +} + +const DefaultInterceptUrlsMap = { + [InterceptedApiType.AnswerData]: [ + '/prism/?op=GetChartWithData', + '/prism/?op=GetTableWithHeadlineData', + '/prism/?op=GetTableWithData', + ], + [InterceptedApiType.LiveboardData]: [ + '/prism/?op=LoadContextBook' + ], +}; +const formatInterceptUrl = (url) => { + const host = getThoughtSpotHost(getEmbedConfig()); + if (url.startsWith('/')) + return `${host}${url}`; + return url; +}; +/** + * Converts user passed url values to proper urls + * [ANSER_DATA] => ['https://host/pris/op?=op'] + * @param interceptUrls + * @returns + */ +const processInterceptUrls = (interceptUrls) => { + let processedUrls = [...interceptUrls]; + Object.entries(DefaultInterceptUrlsMap).forEach(([apiType, apiTypeUrls]) => { + if (!processedUrls.includes(apiType)) + return; + processedUrls = processedUrls.filter(url => url !== apiType); + processedUrls = [...processedUrls, ...apiTypeUrls]; + }); + return processedUrls.map(url => formatInterceptUrl(url)); +}; +/** + * Returns the data to be sent to embed to setup intercepts + * the urls to intercept, timeout etc + * @param viewConfig + * @returns + */ +const getInterceptInitData = (viewConfig) => { + const combinedUrls = [...(viewConfig.interceptUrls || [])]; + if (viewConfig.isOnBeforeGetVizDataInterceptEnabled) { + combinedUrls.push(InterceptedApiType.AnswerData); + } + const shouldInterceptAll = combinedUrls.includes(InterceptedApiType.ALL); + const interceptUrls = shouldInterceptAll ? [InterceptedApiType.ALL] : processInterceptUrls(combinedUrls); + const interceptTimeout = viewConfig.interceptTimeout; + return { + interceptUrls, + interceptTimeout, + }; +}; +const parseJson = (jsonString) => { + try { + const json = JSON.parse(jsonString); + return [json, null]; + } + catch (error) { + return [null, error]; + } +}; +/** + * Parse the api intercept data and return the parsed data and error if any + * Embed returns the input and init from the fetch call + */ +const parseInterceptData = (eventDataString) => { + try { + const [parsedData, error] = parseJson(eventDataString); + if (error) { + return [null, error]; + } + const { input, init } = parsedData; + const [parsedBody, bodyParseError] = parseJson(init.body); + if (!bodyParseError) { + init.body = parsedBody; + } + const parsedInit = { input, init }; + return [parsedInit, null]; + } + catch (error) { + return [null, error]; + } +}; +const getUrlType = (url) => { + for (const [apiType, apiTypeUrls] of Object.entries(DefaultInterceptUrlsMap)) { + if (apiTypeUrls.includes(url)) + return apiType; + } + // TODO: have a unknown type maybe ?? + return InterceptedApiType.ALL; +}; +/** + * Handle Api intercept event and simulate legacy onBeforeGetVizDataIntercept event + * + * embed sends -> ApiIntercept -> we send + * ApiIntercept + * OnBeforeGetVizDataIntercept (if url is part of DefaultUrlMap.AnswerData) + * + * @param params + * @returns + */ +const handleInterceptEvent = async (params) => { + const { eventData, executeEvent, viewConfig, getUnsavedAnswerTml } = params; + const [interceptData, bodyParseError] = parseInterceptData(eventData.data); + if (bodyParseError) { + const errorDetails = { + errorType: ErrorDetailsTypes.API, + message: ERROR_MESSAGE.ERROR_PARSING_API_INTERCEPT_BODY, + code: EmbedErrorCodes.PARSING_API_INTERCEPT_BODY_ERROR, + error: ERROR_MESSAGE.ERROR_PARSING_API_INTERCEPT_BODY, + }; + executeEvent(EmbedEvent.Error, errorDetails); + logger$3.error('Error parsing request body', bodyParseError); + return; + } + const { input: requestUrl, init } = interceptData; + const sessionId = init?.body?.variables?.session?.sessionId; + const vizId = init?.body?.variables?.contextBookId; + const answerDataUrls = DefaultInterceptUrlsMap[InterceptedApiType.AnswerData]; + const legacyInterceptEnabled = viewConfig.isOnBeforeGetVizDataInterceptEnabled; + const isAnswerDataUrl = answerDataUrls.includes(requestUrl); + const sendLegacyIntercept = isAnswerDataUrl && legacyInterceptEnabled; + if (sendLegacyIntercept) { + const answerTml = await getUnsavedAnswerTml({ sessionId, vizId }); + // Build the legacy payload for backwards compatibility + const legacyPayload = { + data: { + data: answerTml, + status: embedEventStatus.END, + type: EmbedEvent.OnBeforeGetVizDataIntercept + } + }; + executeEvent(EmbedEvent.OnBeforeGetVizDataIntercept, legacyPayload); + } + const urlType = getUrlType(requestUrl); + executeEvent(EmbedEvent.ApiIntercept, { ...interceptData, urlType }); +}; +/** + * Support both the legacy and new format of the api intercept response + * @param payload + * @returns + */ +const processApiInterceptResponse = (payload) => { + const isLegacyFormat = payload?.data?.error; + if (isLegacyFormat) { + return processLegacyInterceptResponse(payload); + } + return payload; +}; +const processLegacyInterceptResponse = (payload) => { + const errorText = payload?.data?.error?.errorText; + const errorDescription = payload?.data?.error?.errorDescription; + const payloadToSend = { + execute: payload?.data?.execute, + response: { + body: { + errors: [ + { + title: errorText, + description: errorDescription, + isUserError: true, + }, + ], + data: {}, + }, + }, + }; + return { data: payloadToSend }; +}; + +/** + * Copyright (c) 2022 + * + * Base classes + * @summary Base classes + * @author Ayon Ghosh + */ +/** + * Global prefix for all ThoughtSpot postHash Params. + */ +const THOUGHTSPOT_PARAM_PREFIX = 'ts-'; +const TS_EMBED_ID = '_thoughtspot-embed'; +const VERSION = packageInfo.version; +/** + * The event id map from v2 event names to v1 event id + * v1 events are the classic embed events implemented in Blink v1 + * We cannot rename v1 event types to maintain backward compatibility + * @internal + */ +const V1EventMap = {}; +/** + * Base class for embedding v2 experience + * Note: the v2 version of ThoughtSpot Blink is built on the new stack: + * React+GraphQL + */ +class TsEmbed { + /** + * Setter for the iframe element + * @param {HTMLIFrameElement} iFrame HTMLIFrameElement + */ + setIframeElement(iFrame) { + this.iFrame = iFrame; + this.hostEventClient.setIframeElement(iFrame); + } + constructor(domSelector, viewConfig) { + /** + * The key to store the embed instance in the DOM node + */ + this.embedNodeKey = '__tsEmbed'; + this.isAppInitialized = false; + /** + * Should we encode URL Query Params using base64 encoding which ThoughtSpot + * will generate for embedding. This provides additional security to + * ThoughtSpot clusters against Cross site scripting attacks. + * @default false + */ + this.shouldEncodeUrlQueryParams = false; + this.defaultHiddenActions = [Action.ReportError]; + /** + * Handler for fullscreen change events + */ + this.fullscreenChangeHandler = null; + this.subscribedListeners = {}; + this.messageEventListener = (event) => { + const eventType = this.getEventType(event); + const eventPort = this.getEventPort(event); + const eventData = this.formatEventData(event, eventType); + if (event.source === this.iFrame.contentWindow) { + const processedEventData = processEventData(eventType, eventData, this.thoughtSpotHost, this.isPreRendered ? this.preRenderWrapper : this.hostElement); + if (eventType === EmbedEvent.ApiIntercept) { + this.handleApiInterceptEvent({ eventData, eventPort }); + return; + } + this.executeCallbacks(eventType, processedEventData, eventPort); + } + }; + /** + * Send Custom style as part of payload of APP_INIT + * @param _ + * @param responder + */ + this.appInitCb = async (_, responder) => { + try { + const appInitData = await this.getAppInitData(); + this.isAppInitialized = true; + responder({ + type: EmbedEvent.APP_INIT, + data: appInitData, + }); + } + catch (e) { + logger$3.error(`AppInit failed, Error : ${e?.message}`); + } + }; + this.handleAuthFailure = (error) => { + logger$3.error(`${ERROR_MESSAGE.INVALID_TOKEN_ERROR} Error : ${error?.message}`); + processAuthFailure(error, this.isPreRendered ? this.preRenderWrapper : this.hostElement); + }; + /** + * Refresh the auth token if the autoLogin is true and the authType is TrustedAuthTokenCookieless + * @param _ + * @param responder + */ + this.tokenRefresh = async (_, responder) => { + try { + await this.refreshAuthTokenForCookieless(responder, EmbedEvent.RefreshAuthToken, true); + } + catch (e) { + this.handleAuthFailure(e); + } + }; + /** + * Sends updated auth token to the iFrame to avoid user logout + * @param _ + * @param responder + */ + this.updateAuthToken = async (_, responder) => { + const { authType, autoLogin: autoLoginConfig } = this.embedConfig; + // Default autoLogin: true for cookieless if undefined/null, otherwise + // false + const autoLogin = autoLoginConfig ?? (authType === AuthType.TrustedAuthTokenCookieless); + try { + await this.refreshAuthTokenForCookieless(responder, EmbedEvent.AuthExpire, false); + } + catch (e) { + this.handleAuthFailure(e); + } + if (autoLogin && authType !== AuthType.TrustedAuthTokenCookieless) { + handleAuth(); + } + notifyAuthFailure(AuthFailureType.EXPIRY); + }; + /** + * Auto Login and send updated authToken to the iFrame to avoid user session logout + * @param _ + * @param responder + */ + this.idleSessionTimeout = (_, responder) => { + handleAuth().then(async () => { + let authToken = ''; + try { + authToken = await getAuthenticationToken(this.embedConfig); + responder({ + type: EmbedEvent.IdleSessionTimeout, + data: { authToken }, + }); + } + catch (e) { + this.handleAuthFailure(e); + } + }).catch((e) => { + logger$3.error(`Auto Login failed, Error : ${e?.message}`); + }); + notifyAuthFailure(AuthFailureType.IDLE_SESSION_TIMEOUT); + }; + /** + * Register APP_INIT event and sendback init payload + */ + this.registerAppInit = () => { + this.on(EmbedEvent.APP_INIT, this.appInitCb, { start: false }, true); + this.on(EmbedEvent.AuthExpire, this.updateAuthToken, { start: false }, true); + this.on(EmbedEvent.IdleSessionTimeout, this.idleSessionTimeout, { start: false }, true); + const embedListenerReadyHandler = this.createEmbedContainerHandler(EmbedEvent.EmbedListenerReady); + this.on(EmbedEvent.EmbedListenerReady, embedListenerReadyHandler, { start: false }, true); + const authInitHandler = this.createEmbedContainerHandler(EmbedEvent.AuthInit); + this.on(EmbedEvent.AuthInit, authInitHandler, { start: false }, true); + this.on(EmbedEvent.RefreshAuthToken, this.tokenRefresh, { start: false }, true); + }; + this.showPreRenderByDefault = false; + /** + * We can process the customer given payload before sending it to the embed port + * Embed event handler -> responder -> createEmbedEventResponder -> send response + * @param eventPort The event port for a specific MessageChannel + * @param eventType The event type + * @returns + */ + this.createEmbedEventResponder = (eventPort, eventType) => { + const getPayloadToSend = (payload) => { + if (eventType === EmbedEvent.OnBeforeGetVizDataIntercept) { + return processLegacyInterceptResponse(payload); + } + if (eventType === EmbedEvent.ApiIntercept) { + return processApiInterceptResponse(payload); + } + return payload; + }; + return (payload) => { + const payloadToSend = getPayloadToSend(payload); + this.triggerEventOnPort(eventPort, payloadToSend); + }; + }; + /** + * @hidden + * Internal state to track if the embed container is loaded. + * This is used to trigger events after the embed container is loaded. + */ + this.isEmbedContainerLoaded = false; + /** + * @hidden + * Internal state to track the callbacks to be executed after the embed container + * is loaded. + * This is used to trigger events after the embed container is loaded. + */ + this.embedContainerReadyCallbacks = []; + this.createEmbedContainerHandler = (source) => () => { + const processEmbedContainerReady = () => { + logger$3.debug('processEmbedContainerReady'); + this.isEmbedContainerLoaded = true; + this.executeEmbedContainerReadyCallbacks(); + }; + if (source === EmbedEvent.AuthInit) { + const AUTH_INIT_FALLBACK_DELAY = 1000; + // Wait for 1 second to ensure the embed container is loaded + // This is a workaround to ensure the embed container is loaded + // this is needed until all clusters have EmbedListenerReady event + setTimeout(processEmbedContainerReady, AUTH_INIT_FALLBACK_DELAY); + } + else if (source === EmbedEvent.EmbedListenerReady) { + processEmbedContainerReady(); + } + }; + this.hostElement = getDOMNode(domSelector); + this.eventHandlerMap = new Map(); + this.isError = false; + this.viewConfig = { + excludeRuntimeFiltersfromURL: true, + excludeRuntimeParametersfromURL: true, + ...viewConfig, + }; + this.registerAppInit(); + uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_EMBED_CREATE, { + ...viewConfig, + sdkVersion: VERSION, + }); + const embedConfig = getEmbedConfig(); + if (embedConfig) { + this.embedConfig = embedConfig; + this.thoughtSpotHost = getThoughtSpotHost(embedConfig); + this.thoughtSpotV2Base = getV2BasePath(embedConfig); + } + this.hostEventClient = new HostEventClient(this.iFrame); + this.shouldWaitForRenderPromise = !getIsInitCompleted(); + const afterInit = () => { + this.embedConfig = embedConfig; + if (!embedConfig.authTriggerContainer && !embedConfig.useEventForSAMLPopup) { + this.embedConfig.authTriggerContainer = domSelector; + } + this.thoughtSpotHost = getThoughtSpotHost(embedConfig); + this.thoughtSpotV2Base = getV2BasePath(embedConfig); + this.shouldEncodeUrlQueryParams = embedConfig.shouldEncodeUrlQueryParams; + }; + if (!this.shouldWaitForRenderPromise) { + afterInit(); + } + else { + this.isReadyForRenderPromise = getInitPromise().then(afterInit).finally(() => { + this.shouldWaitForRenderPromise = true; + }); + } + } + /** + * Throws error encountered during initialization. + */ + throwInitError() { + this.handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.INIT_SDK_REQUIRED, + code: EmbedErrorCodes.INIT_ERROR, + error: ERROR_MESSAGE.INIT_SDK_REQUIRED, + }); + } + /** + * Handles errors within the SDK + * @param error The error message or object + * @param errorDetails The error details + */ + handleError(errorDetails) { + this.isError = true; + this.executeCallbacks(EmbedEvent.Error, errorDetails); + // Log error + logger$3.error(errorDetails); + } + /** + * Extracts the type field from the event payload + * @param event The window message event + */ + getEventType(event) { + return event.data?.type || event.data?.__type; + } + /** + * Extracts the port field from the event payload + * @param event The window message event + * @returns + */ + getEventPort(event) { + if (event.ports.length && event.ports[0]) { + return event.ports[0]; + } + return null; + } + /** + * Checks if preauth cache is enabled + * from the view config and embed config + * @returns boolean + */ + isPreAuthCacheEnabled() { + // Disable preauth cache when: + // 1. overrideOrgId is present since: + // - cached auth info would be for wrong org + // - info call response changes for each different overrideOrgId + // 2. disablePreauthCache is explicitly set to true + // 3. FullAppEmbed has primary navbar visible since: + // - primary navbar requires fresh auth state for navigation + // - cached auth may not reflect current user permissions + const isDisabled = (this.viewConfig.overrideOrgId !== undefined + || this.embedConfig.disablePreauthCache === true + || this.isFullAppEmbedWithVisiblePrimaryNavbar()); + return !isDisabled; + } + /** + * Checks if current embed is FullAppEmbed with visible primary navbar + * @returns boolean + */ + isFullAppEmbedWithVisiblePrimaryNavbar() { + const appViewConfig = this.viewConfig; + // Check if this is a FullAppEmbed (AppEmbed) + // showPrimaryNavbar defaults to true if not explicitly set to false + return (appViewConfig.embedComponentType === 'AppEmbed' + && appViewConfig.showPrimaryNavbar === true); + } + /** + * fix for ts7.sep.cl + * will be removed for ts7.oct.cl + * @param event + * @param eventType + * @hidden + */ + formatEventData(event, eventType) { + const eventData = { + ...event.data, + type: eventType, + }; + if (!eventData.data) { + eventData.data = event.data.payload; + } + return eventData; + } + /** + * Subscribe to network events (online/offline) that should + * work regardless of auth status + */ + subscribeToNetworkEvents() { + this.unsubscribeToNetworkEvents(); + const onlineEventListener = (e) => { + this.trigger(HostEvent.Reload); + }; + window.addEventListener('online', onlineEventListener); + const offlineEventListener = (e) => { + const errorDetails = { + errorType: ErrorDetailsTypes.NETWORK, + message: ERROR_MESSAGE.OFFLINE_WARNING, + code: EmbedErrorCodes.NETWORK_ERROR, + offlineWarning: ERROR_MESSAGE.OFFLINE_WARNING, + }; + this.executeCallbacks(EmbedEvent.Error, errorDetails); + logger$3.warn(errorDetails); + }; + window.addEventListener('offline', offlineEventListener); + this.subscribedListeners.online = onlineEventListener; + this.subscribedListeners.offline = offlineEventListener; + } + handleApiInterceptEvent({ eventData, eventPort }) { + const executeEvent = (_eventType, data) => { + this.executeCallbacks(_eventType, data, eventPort); + }; + const getUnsavedAnswerTml = async (props) => { + const response = await this.triggerUIPassThrough(UIPassthroughEvent.GetUnsavedAnswerTML, props); + return response.filter((item) => item.value)?.[0]?.value; + }; + handleInterceptEvent({ eventData, executeEvent, viewConfig: this.viewConfig, getUnsavedAnswerTml }); + } + /** + * Subscribe to message events that depend on successful iframe setup + */ + subscribeToMessageEvents() { + this.unsubscribeToMessageEvents(); + window.addEventListener('message', this.messageEventListener); + this.subscribedListeners.message = this.messageEventListener; + } + /** + * Adds event listeners for both network and message events. + * This maintains backward compatibility with the existing method. + * Adds a global event listener to window for "message" events. + * ThoughtSpot detects if a particular event is targeted to this + * embed instance through an identifier contained in the payload, + * and executes the registered callbacks accordingly. + */ + subscribeToEvents() { + this.subscribeToNetworkEvents(); + this.subscribeToMessageEvents(); + } + unsubscribeToNetworkEvents() { + if (this.subscribedListeners.online) { + window.removeEventListener('online', this.subscribedListeners.online); + delete this.subscribedListeners.online; + } + if (this.subscribedListeners.offline) { + window.removeEventListener('offline', this.subscribedListeners.offline); + delete this.subscribedListeners.offline; + } + } + unsubscribeToMessageEvents() { + if (this.subscribedListeners.message) { + window.removeEventListener('message', this.subscribedListeners.message); + delete this.subscribedListeners.message; + } + } + unsubscribeToEvents() { + Object.keys(this.subscribedListeners).forEach((key) => { + window.removeEventListener(key, this.subscribedListeners[key]); + }); + } + async getAuthTokenForCookielessInit() { + let authToken = ''; + if (this.embedConfig.authType !== AuthType.TrustedAuthTokenCookieless) + return authToken; + try { + authToken = await getAuthenticationToken(this.embedConfig); + } + catch (e) { + processAuthFailure(e, this.isPreRendered ? this.preRenderWrapper : this.hostElement); + throw e; + } + return authToken; + } + async getDefaultAppInitData() { + const authToken = await this.getAuthTokenForCookielessInit(); + const customActionsResult = getCustomActions([ + ...(this.viewConfig.customActions || []), + ...(this.embedConfig.customActions || []) + ]); + if (customActionsResult.errors.length > 0) { + this.handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: customActionsResult.errors, + code: EmbedErrorCodes.CUSTOM_ACTION_VALIDATION, + error: { type: EmbedErrorCodes.CUSTOM_ACTION_VALIDATION, message: customActionsResult.errors } + }); + } + const baseInitData = { + customisations: getCustomisations(this.embedConfig, this.viewConfig), + authToken, + runtimeFilterParams: this.viewConfig.excludeRuntimeFiltersfromURL + ? getRuntimeFilters(this.viewConfig.runtimeFilters) + : null, + runtimeParameterParams: this.viewConfig.excludeRuntimeParametersfromURL + ? getRuntimeParameters(this.viewConfig.runtimeParameters || []) + : null, + hiddenHomepageModules: this.viewConfig.hiddenHomepageModules || [], + reorderedHomepageModules: this.viewConfig.reorderedHomepageModules || [], + hostConfig: this.embedConfig.hostConfig, + hiddenHomeLeftNavItems: this.viewConfig?.hiddenHomeLeftNavItems + ? this.viewConfig?.hiddenHomeLeftNavItems + : [], + customVariablesForThirdPartyTools: this.embedConfig.customVariablesForThirdPartyTools || {}, + hiddenListColumns: this.viewConfig.hiddenListColumns || [], + customActions: customActionsResult.actions, + embedExpiryInAuthToken: this.viewConfig.refreshAuthTokenOnNearExpiry ?? true, + ...getInterceptInitData(this.viewConfig), + ...getHostEventsConfig(this.viewConfig), + }; + return baseInitData; + } + async getAppInitData() { + return this.getDefaultAppInitData(); + } + /** + * Helper method to refresh/update auth token for TrustedAuthTokenCookieless auth type + * @param responder - Function to send response back + * @param eventType - The embed event type to send + * @param forceRefresh - Whether to force refresh the token + * @returns Promise that resolves if token was refreshed, rejects otherwise + */ + async refreshAuthTokenForCookieless(responder, eventType, forceRefresh = false) { + const { authType, autoLogin } = this.embedConfig; + const isAutoLoginTrue = autoLogin ?? (authType === AuthType.TrustedAuthTokenCookieless); + if (isAutoLoginTrue && authType === AuthType.TrustedAuthTokenCookieless) { + const authToken = await getAuthenticationToken(this.embedConfig, forceRefresh); + responder({ + type: eventType, + data: { authToken }, + }); + } + } + /** + * Constructs the base URL string to load the ThoughtSpot app. + * @param query + */ + getEmbedBasePath(query) { + let queryString = query.startsWith('?') ? query : `?${query}`; + if (this.shouldEncodeUrlQueryParams) { + queryString = `?base64UrlEncodedFlags=${getEncodedQueryParamsString(queryString.substr(1))}`; + } + const basePath = [this.thoughtSpotHost, this.thoughtSpotV2Base, queryString] + .filter((x) => x.length > 0) + .join('/'); + return `${basePath}#`; + } + async getUpdateEmbedParamsObject() { + let queryParams = this.getEmbedParamsObject(); + const appInitData = await this.getAppInitData(); + queryParams = { ...this.viewConfig, ...queryParams, ...appInitData }; + return queryParams; + } + /** + * Common query params set for all the embed modes. + * @param queryParams + * @returns queryParams + */ + getBaseQueryParams(queryParams = {}) { + let hostAppUrl = window?.location?.host || ''; + // The below check is needed because TS Cloud firewall, blocks + // localhost/127.0.0.1 in any url param. + if (hostAppUrl.includes('localhost') || hostAppUrl.includes('127.0.0.1')) { + hostAppUrl = 'local-host'; + } + const blockNonEmbedFullAppAccess = this.embedConfig.blockNonEmbedFullAppAccess ?? true; + queryParams[Param.EmbedApp] = true; + queryParams[Param.HostAppUrl] = encodeURIComponent(hostAppUrl); + queryParams[Param.ViewPortHeight] = window.innerHeight; + queryParams[Param.ViewPortWidth] = window.innerWidth; + queryParams[Param.Version] = VERSION; + queryParams[Param.AuthType] = this.embedConfig.authType; + queryParams[Param.blockNonEmbedFullAppAccess] = blockNonEmbedFullAppAccess; + queryParams[Param.AutoLogin] = this.embedConfig.autoLogin; + if (this.embedConfig.disableLoginRedirect === true || this.embedConfig.autoLogin === true) { + queryParams[Param.DisableLoginRedirect] = true; + } + if (this.embedConfig.authType === AuthType.EmbeddedSSO) { + queryParams[Param.ForceSAMLAutoRedirect] = true; + } + if (this.embedConfig.authType === AuthType.TrustedAuthTokenCookieless) { + queryParams[Param.cookieless] = true; + } + if (this.embedConfig.pendoTrackingKey) { + queryParams[Param.PendoTrackingKey] = this.embedConfig.pendoTrackingKey; + } + if (this.embedConfig.numberFormatLocale) { + queryParams[Param.NumberFormatLocale] = this.embedConfig.numberFormatLocale; + } + if (this.embedConfig.dateFormatLocale) { + queryParams[Param.DateFormatLocale] = this.embedConfig.dateFormatLocale; + } + if (this.embedConfig.currencyFormat) { + queryParams[Param.CurrencyFormat] = this.embedConfig.currencyFormat; + } + const { disabledActions, disabledActionReason, hiddenActions, visibleActions, hiddenTabs, visibleTabs, showAlerts, additionalFlags: additionalFlagsFromView, locale, customizations, contextMenuTrigger, linkOverride, enableLinkOverridesV2, insertInToSlide, disableRedirectionLinksInNewTab, overrideOrgId, exposeTranslationIDs, primaryAction, } = this.viewConfig; + const { additionalFlags: additionalFlagsFromInit } = this.embedConfig; + const additionalFlags = { + ...additionalFlagsFromInit, + ...additionalFlagsFromView, + }; + if (Array.isArray(visibleActions) && Array.isArray(hiddenActions)) { + this.handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.CONFLICTING_ACTIONS_CONFIG, + code: EmbedErrorCodes.CONFLICTING_ACTIONS_CONFIG, + error: ERROR_MESSAGE.CONFLICTING_ACTIONS_CONFIG, + }); + return queryParams; + } + if (Array.isArray(visibleTabs) && Array.isArray(hiddenTabs)) { + this.handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.CONFLICTING_TABS_CONFIG, + code: EmbedErrorCodes.CONFLICTING_TABS_CONFIG, + error: ERROR_MESSAGE.CONFLICTING_TABS_CONFIG, + }); + return queryParams; + } + if (primaryAction) { + queryParams[Param.PrimaryAction] = primaryAction; + } + if (disabledActions?.length) { + queryParams[Param.DisableActions] = disabledActions; + } + if (disabledActionReason) { + queryParams[Param.DisableActionReason] = disabledActionReason; + } + if (exposeTranslationIDs) { + queryParams[Param.ExposeTranslationIDs] = exposeTranslationIDs; + } + queryParams[Param.HideActions] = [...this.defaultHiddenActions, ...(hiddenActions ?? [])]; + if (Array.isArray(visibleActions)) { + queryParams[Param.VisibleActions] = visibleActions; + } + if (Array.isArray(hiddenTabs)) { + queryParams[Param.HiddenTabs] = hiddenTabs; + } + if (Array.isArray(visibleTabs)) { + queryParams[Param.VisibleTabs] = visibleTabs; + } + /** + * Default behavior for context menu will be left-click + * from version 9.2.0.cl the user have an option to override context + * menu click + */ + if (contextMenuTrigger === ContextMenuTriggerOptions.LEFT_CLICK) { + queryParams[Param.ContextMenuTrigger] = 'left'; + } + else if (contextMenuTrigger === ContextMenuTriggerOptions.RIGHT_CLICK) { + queryParams[Param.ContextMenuTrigger] = 'right'; + } + else if (contextMenuTrigger === ContextMenuTriggerOptions.BOTH_CLICKS) { + queryParams[Param.ContextMenuTrigger] = 'both'; + } + const embedCustomizations = this.embedConfig.customizations; + const spriteUrl = customizations?.iconSpriteUrl || embedCustomizations?.iconSpriteUrl; + if (spriteUrl) { + queryParams[Param.IconSpriteUrl] = spriteUrl.replace('https://', ''); + } + const stringIDsUrl = customizations?.content?.stringIDsUrl + || embedCustomizations?.content?.stringIDsUrl; + if (stringIDsUrl) { + queryParams[Param.StringIDsUrl] = stringIDsUrl; + } + if (showAlerts !== undefined) { + queryParams[Param.ShowAlerts] = showAlerts; + } + if (locale !== undefined) { + queryParams[Param.Locale] = locale; + } + // TODO: Once V2 is stable, send both flags when + // linkOverride is true (remove the else-if). + if (enableLinkOverridesV2) { + queryParams[Param.EnableLinkOverridesV2] = true; + queryParams[Param.LinkOverride] = true; + } + else if (linkOverride) { + queryParams[Param.LinkOverride] = linkOverride; + } + if (insertInToSlide) { + queryParams[Param.ShowInsertToSlide] = insertInToSlide; + } + if (disableRedirectionLinksInNewTab) { + queryParams[Param.DisableRedirectionLinksInNewTab] = disableRedirectionLinksInNewTab; + } + if (overrideOrgId !== undefined) { + queryParams[Param.OverrideOrgId] = overrideOrgId; + } + if (this.isPreAuthCacheEnabled()) { + queryParams[Param.preAuthCache] = true; + } + queryParams[Param.OverrideNativeConsole] = true; + queryParams[Param.ClientLogLevel] = this.embedConfig.logLevel; + if (isObject_1(additionalFlags) && !isEmpty_1(additionalFlags)) { + Object.assign(queryParams, additionalFlags); + } + // Do not add any flags below this, as we want additional flags to + // override other flags + return queryParams; + } + /** + * Constructs the base URL string to load v1 of the ThoughtSpot app. + * This is used for embedding Liveboards, visualizations, and full application. + * @param queryString The query string to append to the URL. + * @param isAppEmbed A Boolean parameter to specify if you are embedding + * the full application. + */ + getV1EmbedBasePath(queryString) { + const queryParams = this.shouldEncodeUrlQueryParams + ? `?base64UrlEncodedFlags=${getEncodedQueryParamsString(queryString)}` + : `?${queryString}`; + const host = this.thoughtSpotHost; + const path = `${host}/${queryParams}#`; + return path; + } + getEmbedParams() { + const queryParams = this.getEmbedParamsObject(); + return getQueryParamString(queryParams); + } + getEmbedParamsObject() { + const params = this.getBaseQueryParams(); + return params; + } + getRootIframeSrc() { + const query = this.getEmbedParams(); + return this.getEmbedBasePath(query); + } + createIframeEl(frameSrc) { + const iFrame = document.createElement('iframe'); + iFrame.src = frameSrc; + iFrame.id = TS_EMBED_ID; + iFrame.setAttribute('data-ts-iframe', 'true'); + // according to screenfull.js documentation + // allowFullscreen, webkitallowfullscreen and mozallowfullscreen must be + // true + iFrame.allowFullscreen = true; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + iFrame.webkitallowfullscreen = true; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + iFrame.mozallowfullscreen = true; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + iFrame.allow = 'clipboard-read; clipboard-write; fullscreen; local-network-access;'; + const frameParams = this.viewConfig.frameParams; + const { height: frameHeight, width: frameWidth, ...restParams } = frameParams || {}; + const width = getCssDimension(frameWidth || DEFAULT_EMBED_WIDTH); + const height = getCssDimension(frameHeight || DEFAULT_EMBED_HEIGHT); + setAttributes(iFrame, restParams); + iFrame.style.width = `${width}`; + iFrame.style.height = `${height}`; + // Set minimum height to the frame so that, + // scaling down on the fullheight doesn't make it too small. + iFrame.style.minHeight = `${height}`; + iFrame.style.border = '0'; + iFrame.name = 'ThoughtSpot Embedded Analytics'; + return iFrame; + } + /** + * Returns true if this embed instance is configured for pre-rendering. + */ + isPreRenderEmbed() { + return !!this.viewConfig.preRenderId; + } + handleInsertionIntoDOM(child) { + if (this.isPreRenderEmbed()) { + this.insertIntoDOMForPreRender(child); + } + else { + this.insertIntoDOM(child); + } + if (this.insertedDomEl instanceof Node) { + this.insertedDomEl[this.embedNodeKey] = this; + } + if (this.preRenderWrapper) { + this.preRenderWrapper[this.embedNodeKey] = this; + } + } + /** + * Renders the embedded ThoughtSpot app in an iframe and sets up + * event listeners. + * @param url - The URL of the embedded ThoughtSpot app. + */ + async renderIFrame(url) { + if (this.isError) { + return null; + } + if (!this.thoughtSpotHost) { + this.throwInitError(); + } + if (url.length > URL_MAX_LENGTH) ; + return renderInQueue((nextInQueue) => { + const initTimestamp = Date.now(); + this.executeCallbacks(EmbedEvent.Init, { + data: { + timestamp: initTimestamp, + }, + type: EmbedEvent.Init, + }); + uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_RENDER_START); + // Always subscribe to network events, regardless of auth status + this.subscribeToNetworkEvents(); + return getAuthPromise() + ?.then((isLoggedIn) => { + if (!isLoggedIn) { + this.handleInsertionIntoDOM(this.embedConfig.loginFailedMessage); + return; + } + this.setIframeElement(this.iFrame || this.createIframeEl(url)); + this.iFrame.addEventListener('load', () => { + nextInQueue(); + const loadTimestamp = Date.now(); + this.executeCallbacks(EmbedEvent.Load, { + data: { + timestamp: loadTimestamp, + }, + type: EmbedEvent.Load, + }); + uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_RENDER_COMPLETE, { + elWidth: this.iFrame.clientWidth, + elHeight: this.iFrame.clientHeight, + timeTookToLoad: loadTimestamp - initTimestamp, + }); + // Send info event if preauth cache is enabled + if (this.isPreAuthCacheEnabled()) { + getPreauthInfo().then((data) => { + if (data?.info) { + this.trigger(HostEvent.InfoSuccess, data); + } + }); + } + // Setup fullscreen change handler after iframe is + // loaded and ready + this.setupFullscreenChangeHandler(); + }); + this.iFrame.addEventListener('error', () => { + nextInQueue(); + }); + this.handleInsertionIntoDOM(this.iFrame); + const prefetchIframe = document.querySelectorAll('.prefetchIframe'); + if (prefetchIframe.length) { + prefetchIframe.forEach((el) => { + el.remove(); + }); + } + // Subscribe to message events only after successful + // auth and iframe setup + this.subscribeToMessageEvents(); + }) + .catch((error) => { + nextInQueue(); + uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_RENDER_FAILED, { + error: JSON.stringify(error), + }); + this.handleInsertionIntoDOM(this.embedConfig.loginFailedMessage); + this.handleError({ + errorType: ErrorDetailsTypes.API, + message: error.message || ERROR_MESSAGE.LOGIN_FAILED, + code: EmbedErrorCodes.LOGIN_FAILED, + error: error, + }); + }); + }); + } + createPreRenderWrapper() { + const preRenderIds = this.getPreRenderIds(); + document.getElementById(preRenderIds.wrapper)?.remove(); + const preRenderWrapper = document.createElement('div'); + preRenderWrapper.id = preRenderIds.wrapper; + const initialPreRenderWrapperStyle = { + position: 'absolute', + top: '0', + left: '0', + width: '100vw', + height: '100vh', + }; + setStyleProperties(preRenderWrapper, initialPreRenderWrapperStyle); + return preRenderWrapper; + } + /** + * Checks for an existing pre-rendered component and connects to it. + * + * If a matching pre-rendered component is found in the DOM, this method + * sets the internal properties of the embed object to reference it. + * + * @returns True if a connection was successfully established, false otherwise. + */ + connectPreRendered() { + const preRenderIds = this.getPreRenderIds(); + const preRenderWrapperElement = document.getElementById(preRenderIds.wrapper); + this.preRenderWrapper = this.preRenderWrapper || preRenderWrapperElement; + this.preRenderChild = this.preRenderChild || document.getElementById(preRenderIds.child); + if (this.preRenderWrapper && this.preRenderChild) { + this.isPreRendered = true; + if (this.preRenderChild instanceof HTMLIFrameElement) { + this.setIframeElement(this.preRenderChild); + } + this.isRendered = true; + } + return this.isPreRenderConnected(); + } + isPreRenderConnected() { + return (Boolean(this.preRenderWrapper && this.preRenderChild)); + } + createPreRenderChild(child) { + const preRenderIds = this.getPreRenderIds(); + document.getElementById(preRenderIds.child)?.remove(); + if (child instanceof HTMLElement) { + child.id = preRenderIds.child; + return child; + } + const divChildNode = document.createElement('div'); + setStyleProperties(divChildNode, { width: '100%', height: '100%' }); + divChildNode.id = preRenderIds.child; + if (typeof child === 'string') { + divChildNode.innerHTML = child; + } + else { + divChildNode.appendChild(child); + } + return divChildNode; + } + /** + * Creates the in-flow placeholder div inserted into the host element when + * showPreRender() is called. The wrapper observes this element to stay + * aligned with the host layout. + */ + createPreRenderPlaceholder() { + const placeholder = document.createElement('div'); + const id = this.getPreRenderIds(); + const { width: frameWidth, height: frameHeight } = this.viewConfig.frameParams || {}; + const width = getCssDimension(frameWidth || DEFAULT_EMBED_WIDTH); + const height = getCssDimension(frameHeight || DEFAULT_EMBED_HEIGHT); + placeholder.style.width = width; + placeholder.style.height = height; + // we can improve this , lol + placeholder.id = id.placeHolder; + return placeholder; + } + insertIntoDOMForPreRender(child) { + const preRenderChild = this.createPreRenderChild(child); + const preRenderWrapper = this.createPreRenderWrapper(); + preRenderWrapper.appendChild(preRenderChild); + this.preRenderChild = preRenderChild; + this.preRenderWrapper = preRenderWrapper; + if (preRenderChild instanceof HTMLIFrameElement) { + this.setIframeElement(preRenderChild); + } + if (this.iFrame) { + this.iFrame.style.height = '100%'; + this.iFrame.style.width = '100%'; + } + if (this.showPreRenderByDefault) { + this.showPreRender(); + } + else { + this.hidePreRender(); + } + document.body.appendChild(preRenderWrapper); + } + insertIntoDOM(child) { + if (this.viewConfig.insertAsSibling) { + if (typeof child === 'string') { + const div = document.createElement('div'); + div.innerHTML = child; + div.id = TS_EMBED_ID; + child = div; + } + if (this.hostElement.nextElementSibling?.id === TS_EMBED_ID) { + this.hostElement.nextElementSibling.remove(); + } + this.hostElement.parentElement.insertBefore(child, this.hostElement.nextSibling); + this.insertedDomEl = child; + } + else if (typeof child === 'string') { + this.hostElement.innerHTML = child; + this.insertedDomEl = this.hostElement.children[0]; + } + else { + this.hostElement.innerHTML = ''; + this.hostElement.appendChild(child); + this.insertedDomEl = child; + } + } + /** + * Sets the height of the iframe + * @param height The height in pixels + */ + setIFrameHeight(height) { + if (this.isPreRendered) { + if (this.insertedDomEl) + this.insertedDomEl.style.height = getCssDimension(height); + else + this.preRenderWrapper.style.height = getCssDimension(height); + } + else { + // normal (non-preRender) mode: size the iframe directly + this.iFrame.style.height = getCssDimension(height); + } + } + shouldSkipEvent(eventType, data) { + const errorType = data?.errorType ?? data?.data?.code; + if (eventType === EmbedEvent.Error + && errorType === EmbedErrorCodes.HOST_EVENT_VALIDATION + && (!getHostEventsConfig(this.viewConfig).useHostEventsV2 || getHostEventsConfig(this.viewConfig).shouldBypassPayloadValidation)) { + logger$3.warn(`Host Event Validation failed: ${data?.data?.message}`); + return true; + } + return false; + } + /** + * Executes all registered event handlers for a particular event type + * @param eventType The event type + * @param data The payload invoked with the event handler + * @param eventPort The event Port for a specific MessageChannel + */ + executeCallbacks(eventType, data, eventPort) { + if (this.shouldSkipEvent(eventType, data)) + return; + const eventHandlers = this.eventHandlerMap.get(eventType) || []; + const allHandlers = this.eventHandlerMap.get(EmbedEvent.ALL) || []; + const callbacks = [...eventHandlers, ...allHandlers]; + const dataStatus = data?.status || embedEventStatus.END; + callbacks.forEach((callbackObj) => { + if ( + // When start status is true it trigger only start releated + // payload + (callbackObj.options.start && dataStatus === embedEventStatus.START) + // When start status is false it trigger only end releated + // payload + || (!callbackObj.options.start && dataStatus === embedEventStatus.END)) { + const responder = this.createEmbedEventResponder(eventPort, eventType); + callbackObj.callback(data, responder); + } + }); + } + /** + * Returns the ThoughtSpot hostname or IP address. + */ + getThoughtSpotHost() { + return this.thoughtSpotHost; + } + /** + * Gets the v1 event type (if applicable) for the EmbedEvent type + * @param eventType The v2 event type + * @returns The corresponding v1 event type if one exists + * or else the v2 event type itself + */ + getCompatibleEventType(eventType) { + return V1EventMap[eventType] || eventType; + } + /** + * Calculates the iframe center for the current visible viewPort + * of iframe using Scroll position of Host App, offsetTop for iframe + * in Host app. ViewPort height of the tab. + * @returns iframe Center in visible viewport, + * Iframe height, + * View port height. + */ + getIframeCenter() { + const offsetTopClient = getOffsetTop(this.iFrame); + const scrollTopClient = window.scrollY; + const viewPortHeight = window.innerHeight; + const iframeHeight = this.iFrame.offsetHeight; + const iframeScrolled = scrollTopClient - offsetTopClient; + let iframeVisibleViewPort; + let iframeOffset; + if (iframeScrolled < 0) { + iframeVisibleViewPort = viewPortHeight - (offsetTopClient - scrollTopClient); + iframeVisibleViewPort = Math.min(iframeHeight, iframeVisibleViewPort); + iframeOffset = 0; + } + else { + iframeVisibleViewPort = Math.min(iframeHeight - iframeScrolled, viewPortHeight); + iframeOffset = iframeScrolled; + } + const iframeCenter = iframeOffset + iframeVisibleViewPort / 2; + return { + iframeCenter, + iframeScrolled, + iframeHeight, + viewPortHeight, + iframeVisibleViewPort, + }; + } + /** + * Registers an event listener to trigger an alert when the ThoughtSpot app + * sends an event of a particular message type to the host application. + * @param messageType The message type + * @param callback A callback as a function + * @param options The message options + * @param isSelf + * @param isRegisteredBySDK + * @example + * ```js + * tsEmbed.on(EmbedEvent.Error, (data) => { + * console.error(data); + * }); + * ``` + * @example + * ```js + * tsEmbed.on(EmbedEvent.Save, (data) => { + * console.log("Answer save clicked", data); + * }, { + * start: true // This will trigger the callback on start of save + * }); + * ``` + */ + on(messageType, callback, options = { start: false }, isRegisteredBySDK = false) { + uploadMixpanelEvent(`${MIXPANEL_EVENT.VISUAL_SDK_ON}-${messageType}`, { + isRegisteredBySDK, + }); + if (this.isRendered) { + logger$3.warn('Please register event handlers before calling render'); + } + const callbacks = this.eventHandlerMap.get(messageType) || []; + callbacks.push({ options, callback }); + this.eventHandlerMap.set(messageType, callbacks); + return this; + } + /** + * Removes an event listener for a particular event type. + * @param messageType The message type + * @param callback The callback to remove + * @example + * ```js + * const errorHandler = (data) => { console.error(data); }; + * tsEmbed.on(EmbedEvent.Error, errorHandler); + * tsEmbed.off(EmbedEvent.Error, errorHandler); + * ``` + */ + off(messageType, callback) { + const callbacks = this.eventHandlerMap.get(messageType) || []; + const index = callbacks.findIndex((cb) => cb.callback === callback); + if (index > -1) { + callbacks.splice(index, 1); + } + return this; + } + /** + * Triggers an event on specific Port registered against + * for the EmbedEvent + * @param eventType The message type + * @param data The payload to send + * @param eventPort + * @param payload + */ + triggerEventOnPort(eventPort, payload) { + if (eventPort) { + try { + eventPort.postMessage({ + type: payload.type, + data: payload.data, + }); + } + catch (e) { + eventPort.postMessage({ error: e }); + logger$3.log(e); + } + } + else { + logger$3.log('Event Port is not defined'); + } + } + getPreRenderObj() { + const embedObj = this.preRenderWrapper?.[this.embedNodeKey]; + if (embedObj === this) { + logger$3.info('embedObj is same as this'); + } + return embedObj; + } + checkEmbedContainerLoaded() { + if (this.isEmbedContainerLoaded) + return true; + const preRenderObj = this.getPreRenderObj(); + if (preRenderObj && preRenderObj.isEmbedContainerLoaded) { + this.isEmbedContainerLoaded = true; + } + return this.isEmbedContainerLoaded; + } + executeEmbedContainerReadyCallbacks() { + logger$3.debug('executePendingEvents', this.embedContainerReadyCallbacks); + this.embedContainerReadyCallbacks.forEach((callback) => { + callback?.(); + }); + this.embedContainerReadyCallbacks = []; + } + /** + * Executes a callback after the embed container is loaded. + * @param callback The callback to execute + */ + executeAfterEmbedContainerLoaded(callback) { + if (this.checkEmbedContainerLoaded()) { + callback?.(); + } + else { + logger$3.debug('pushing callback to embedContainerReadyCallbacks', callback); + this.embedContainerReadyCallbacks.push(callback); + } + } + /** + * Triggers an event to the embedded app + * @param {HostEvent} messageType The event type + * @param {any} data The payload to send with the message + * @param {ContextType} context Optional context type to specify the context from which the event is triggered. + * Use ContextType.Search for search answer context, ContextType.Answer for answer/explore context, + * ContextType.Liveboard for liveboard context, or ContextType.Spotter for spotter context. + * Available from SDK version 1.45.2 | ThoughtSpot: 26.3.0.cl + * @returns A promise that resolves with the response from the embedded app + * @example + * ```js + * // Trigger Pin event with context (SDK: 1.45.2+) + * import { HostEvent, ContextType } from '@thoughtspot/visual-embed-sdk'; + * embed.trigger(HostEvent.Pin, { + * vizId: "123", + * liveboardId: "456" + * }, ContextType.Search); + * ``` + * @version SDK: 1.45.2 | ThoughtSpot: 26.3.0.cl (for context parameter) + */ + async trigger(messageType, data = {}, context) { + uploadMixpanelEvent(`${MIXPANEL_EVENT.VISUAL_SDK_TRIGGER}-${messageType}`); + if (!this.isRendered) { + this.handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.RENDER_BEFORE_EVENTS_REQUIRED, + code: EmbedErrorCodes.RENDER_NOT_CALLED, + error: ERROR_MESSAGE.RENDER_BEFORE_EVENTS_REQUIRED, + }); + return null; + } + if (!messageType) { + this.handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.HOST_EVENT_TYPE_UNDEFINED, + code: EmbedErrorCodes.HOST_EVENT_TYPE_UNDEFINED, + error: ERROR_MESSAGE.HOST_EVENT_TYPE_UNDEFINED, + }); + return null; + } + // Check if iframe exists before triggering - + // this prevents the error when auth fails + if (!this.iFrame) { + logger$3.debug(`Cannot trigger ${messageType} - iframe not available (likely due to auth failure)`); + return null; + } + // send an empty object, this is needed for liveboard default handlers + return this.hostEventClient.triggerHostEvent(messageType, data, context).catch((err) => { + if (err?.isValidationError) { + const errorDetails = err.embedErrorDetails ?? { + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: err.message || ERROR_MESSAGE.UPDATEFILTERS_INVALID_PAYLOAD, + code: EmbedErrorCodes.UPDATEFILTERS_INVALID_PAYLOAD, + error: err.message, + }; + this.handleError(errorDetails); + } + throw err; + }); + } + /** + * Triggers an event to the embedded app, skipping the UI flow. + * @param {UIPassthroughEvent} apiName - The name of the API to be triggered. + * @param {UIPassthroughRequest} parameters - The parameters to be passed to the API. + * @returns {Promise} - A promise that resolves with the response + * from the embedded app. + */ + async triggerUIPassThrough(apiName, parameters) { + const response = this.hostEventClient.triggerUIPassthroughApi(apiName, parameters); + return response; + } + /** + * Marks the ThoughtSpot object to have been rendered + * Needs to be overridden by subclasses to do the actual + * rendering of the iframe. + * @param args + */ + async render() { + if (!getIsInitCalled()) { + logger$3.error(ERROR_MESSAGE.RENDER_CALLED_BEFORE_INIT); + } + if (this.shouldWaitForRenderPromise) + await this.isReadyForRenderPromise; + this.isRendered = true; + return this; + } + getIframeSrc() { + return ''; + } + handleRenderForPrerender() { + return this.render(); + } + /** + * Context object for the embedded component. + * @returns {ContextObject} The current context object containing the page type and object ids. + * @example + * ```js + * const context = await embed.getCurrentContext(); + * console.log(context); + * + * // Example output + * { + * stack: [ + * { + * name: 'Liveboard', + * type: ContextType.Liveboard, + * objectIds: { + * liveboardId: '123', + * }, + * }, + * ], + * currentContext: { + * name: 'Liveboard', + * type: ContextType.Liveboard, + * objectIds: { + * liveboardId: '123', + * }, + * }, + * } + * ``` + * @version SDK: 1.45.2 | ThoughtSpot: 26.3.0.cl + */ + async getCurrentContext() { + return new Promise((resolve) => { + this.executeAfterEmbedContainerLoaded(async () => { + const context = await this.trigger(HostEvent.GetPageContext, {}); + resolve(context); + }); + }); + } + /** + * Generates the event name for a "Subscribed" embed event. + * + * This helper appends the "Subscribed" suffix to a given host or action event, + * allowing you to listen for subscription lifecycle events in a consistent format. + * + * @param eventName - The host or action event to generate the subscribed event name for. + * @returns The formatted event name (e.g., "Save Subscribed"). + * + * @version SDK: 1.47.2 | ThoughtSpot: 26.3.0.cl + */ + subscribedEvent(eventName) { + return `${eventName} ${EmbedEvent.Subscribed}`; + } + /** + * Creates the preRender shell + * @param showPreRenderByDefault - Show the preRender after render, hidden by default + */ + async preRender(showPreRenderByDefault = false, replaceExistingPreRender = false) { + if (!this.viewConfig.preRenderId) { + logger$3.error(ERROR_MESSAGE.PRERENDER_ID_MISSING); + return this; + } + this.isPreRendered = true; + this.showPreRenderByDefault = showPreRenderByDefault; + const isAlreadyRendered = this.connectPreRendered(); + if (isAlreadyRendered && !replaceExistingPreRender) { + if (this.showPreRenderByDefault) { + this.showPreRender(); + } + return this; + } + return this.handleRenderForPrerender(); + } + /** + * Get the Post Url Params for THOUGHTSPOT from the current + * host app URL. + * THOUGHTSPOT URL params starts with a prefix "ts-" + * @version SDK: 1.14.0 | ThoughtSpot: 8.4.0.cl, 8.4.1-sw + */ + getThoughtSpotPostUrlParams(additionalParams = {}) { + const urlHash = window.location.hash; + const queryParams = window.location.search; + const postHashParams = urlHash.split('?'); + const postURLParams = postHashParams[postHashParams.length - 1]; + const queryParamsObj = new URLSearchParams(queryParams); + const postURLParamsObj = new URLSearchParams(postURLParams); + const params = new URLSearchParams(); + const addKeyValuePairCb = (value, key) => { + if (key.startsWith(THOUGHTSPOT_PARAM_PREFIX)) { + params.append(key, value); + } + }; + queryParamsObj.forEach(addKeyValuePairCb); + postURLParamsObj.forEach(addKeyValuePairCb); + Object.entries(additionalParams).forEach(([k, v]) => params.append(k, v)); + let tsParams = params.toString(); + tsParams = tsParams ? `?${tsParams}` : ''; + return tsParams; + } + /** + * Destroys the ThoughtSpot embed, and remove any nodes from the DOM. + * @version SDK: 1.19.1 | ThoughtSpot: * + */ + destroy() { + try { + this.removeFullscreenChangeHandler(); + this.unsubscribeToEvents(); + this.preRenderWrapper?.remove(); + if (!this.isRendered) { + return; + } + if (!getEmbedConfig().waitForCleanupOnDestroy) { + this.trigger(HostEvent.DestroyEmbed); + this.insertedDomEl?.parentNode?.removeChild(this.insertedDomEl); + } + else { + const cleanupTimeout = getEmbedConfig().cleanupTimeout; + Promise.race([ + this.trigger(HostEvent.DestroyEmbed), + new Promise((resolve) => setTimeout(resolve, cleanupTimeout)), + ]).catch((e) => { + logger$3.log('Error destroying TS Embed', e); + }).finally(() => { + try { + this.insertedDomEl?.parentNode?.removeChild(this.insertedDomEl); + } + catch (e) { + logger$3.log('Error removing DOM element on destroy', e); + } + }); + } + } + catch (e) { + logger$3.log('Error destroying TS Embed', e); + } + } + getUnderlyingFrameElement() { + return this.iFrame; + } + /** + * Prerenders a generic instance of the TS component. + * This means without the path but with the flags already applied. + * This is useful for prerendering the component in the background. + * @version SDK: 1.22.0 + * @returns + */ + async prerenderGeneric() { + if (!getIsInitCalled()) { + logger$3.error(ERROR_MESSAGE.RENDER_CALLED_BEFORE_INIT); + } + if (this.shouldWaitForRenderPromise) + await this.isReadyForRenderPromise; + const prerenderFrameSrc = this.getRootIframeSrc(); + this.isRendered = true; + return this.renderIFrame(prerenderFrameSrc); + } + beforePrerenderVisible() { + // We can ignore this as its a bit expensive and the newer customers + // have moved on to UpdateEmbedParams supported clusters + // this.validatePreRenderViewConfig(this.viewConfig); removed in #517 + logger$3.debug('triggering UpdateEmbedParams', this.viewConfig); + this.executeAfterEmbedContainerLoaded(async () => { + try { + const params = await this.getUpdateEmbedParamsObject(); + this.trigger(HostEvent.UpdateEmbedParams, params); + } + catch (error) { + logger$3.error(ERROR_MESSAGE.UPDATE_PARAMS_FAILED, error); + this.handleError({ + errorType: ErrorDetailsTypes.API, + message: error?.message || ERROR_MESSAGE.UPDATE_PARAMS_FAILED, + code: EmbedErrorCodes.UPDATE_PARAMS_FAILED, + error: error?.message || error, + }); + } + }); + } + /** + * Displays the pre-rendered component inside the host element. + * If the component has not been pre-rendered yet, it initiates rendering first. + * Inserts a placeholder element into the host and positions the pre-render + * wrapper to overlay it. + */ + async showPreRender() { + if (this.shouldWaitForRenderPromise) + await this.isReadyForRenderPromise; + if (!this.viewConfig.preRenderId) { + logger$3.error(ERROR_MESSAGE.PRERENDER_ID_MISSING); + return this; + } + if (!this.isPreRenderConnected()) { + // this will call showPreRender down the line + return this.preRender(true); + } + this.isRendered = true; + this.beforePrerenderVisible(); + if (this.hostElement) { + this.insertedDomEl = this.createPreRenderPlaceholder(); + if (this.viewConfig.fullHeight) { + // If fullHeight has already sized the wrapper, seed the placeholder + // with the same height so syncPreRenderStyle gets an accurate rect. + const existingHeight = this.preRenderWrapper.style.height; + if (existingHeight) { + this.insertedDomEl.style.height = existingHeight; + } + } + const placeHolderId = this.getPreRenderIds().placeHolder; + const oldEle = this.hostElement.querySelector(`#${placeHolderId}`); + if (oldEle) { + this.hostElement.removeChild(oldEle); + } + this.hostElement.appendChild(this.insertedDomEl); + this.syncPreRenderStyle(); + if (!this.viewConfig.doNotTrackPreRenderSize) { + const observeTarget = this.insertedDomEl ?? this.hostElement; + this.resizeObserver = new ResizeObserver((entries) => { + entries.forEach((entry) => { + if (entry.contentRect && entry.target === observeTarget) { + setStyleProperties(this.preRenderWrapper, { + width: `${entry.contentRect.width}px`, + height: `${entry.contentRect.height}px`, + }); + } + }); + }); + this.resizeObserver.observe(observeTarget); + } + } + removeStyleProperties(this.preRenderWrapper, ['z-index', 'opacity', 'pointer-events', 'overflow']); + this.subscribeToEvents(); + // Setup fullscreen change handler for prerendered components + if (this.iFrame) { + this.setupFullscreenChangeHandler(); + } + return this; + } + getPreRenderPlaceHolderElement() { + return this.insertedDomEl; + } + /** + * Synchronizes the style properties of the PreRender component with the embedding + * element. This function adjusts the position, width, and height of the PreRender + * component + * to match the dimensions and position of the embedding element. + * @throws {Error} Throws an error if the embedding element (passed as domSelector) + * is not defined or not found. + */ + syncPreRenderStyle() { + if (!this.isPreRenderConnected() || !this.getPreRenderPlaceHolderElement()) { + logger$3.error(ERROR_MESSAGE.SYNC_STYLE_CALLED_BEFORE_RENDER); + return; + } + const elBoundingClient = this.getPreRenderPlaceHolderElement().getBoundingClientRect(); + setStyleProperties(this.preRenderWrapper, { + top: `${elBoundingClient.y + window.scrollY}px`, + left: `${elBoundingClient.x + window.scrollX}px`, + width: `${elBoundingClient.width}px`, + height: `${elBoundingClient.height}px`, + position: 'absolute' + }); + } + /** + * Hides the PreRender component if it is available. + * If the component is not preRendered, it issues a warning. + */ + hidePreRender() { + logger$3.debug('HidePreRender Called'); + if (!this.isPreRenderConnected()) { + // if the embed component is not preRendered , nothing to hide + logger$3.warn('PreRender should be called before hiding it using hidePreRender.'); + return; + } + const preRenderHideStyles = { + opacity: '0', + pointerEvents: 'none', + zIndex: '-1000', + position: 'absolute', + top: '0', + left: '0', + overflow: 'hidden', + }; + setStyleProperties(this.preRenderWrapper, preRenderHideStyles); + if (this.resizeObserver) { + this.resizeObserver.disconnect(); + } + const placeHolderEle = this.getPreRenderPlaceHolderElement(); + if (placeHolderEle) { + placeHolderEle.parentElement.removeChild(placeHolderEle); + } + this.unsubscribeToEvents(); + } + /** + * Retrieves unique HTML element IDs for PreRender-related elements. + * These IDs are constructed based on the provided 'preRenderId' from 'viewConfig'. + * @returns {object} An object containing the IDs for the PreRender elements. + * @property {string} wrapper - The HTML element ID for the PreRender wrapper. + * @property {string} child - The HTML element ID for the PreRender child. + */ + getPreRenderIds() { + return { + wrapper: `tsEmbed-pre-render-wrapper-${this.viewConfig.preRenderId}`, + child: `tsEmbed-pre-render-child-${this.viewConfig.preRenderId}`, + placeHolder: `tsEmbed-pre-render-placeholder-${this.viewConfig.preRenderId}`, + }; + } + /** + * Returns the answerService which can be used to make arbitrary graphql calls on top + * session. + * @param vizId [Optional] to get for a specific viz in case of a Liveboard. + * @version SDK: 1.25.0 | ThoughtSpot: 9.10.0 + */ + async getAnswerService(vizId) { + const { session } = await this.trigger(HostEvent.GetAnswerSession, vizId ? { vizId } : {}); + return new AnswerService(session, null, this.embedConfig.thoughtSpotHost); + } + /** + * Set up fullscreen change detection to automatically trigger ExitPresentMode + * when user exits fullscreen mode + */ + setupFullscreenChangeHandler() { + const embedConfig = getEmbedConfig(); + const disableFullscreenPresentation = embedConfig?.disableFullscreenPresentation ?? true; + if (disableFullscreenPresentation) { + return; + } + if (this.fullscreenChangeHandler) { + document.removeEventListener('fullscreenchange', this.fullscreenChangeHandler); + } + this.fullscreenChangeHandler = () => { + const isFullscreen = !!document.fullscreenElement; + if (!isFullscreen) { + logger$3.info('Exited fullscreen mode - triggering ExitPresentMode'); + // Only trigger if iframe is available and contentWindow is + // accessible + if (this.iFrame && this.iFrame.contentWindow) { + this.trigger(HostEvent.ExitPresentMode); + } + else { + logger$3.debug('Skipping ExitPresentMode - iframe contentWindow not available'); + } + } + }; + document.addEventListener('fullscreenchange', this.fullscreenChangeHandler); + } + /** + * Remove fullscreen change handler + */ + removeFullscreenChangeHandler() { + if (this.fullscreenChangeHandler) { + document.removeEventListener('fullscreenchange', this.fullscreenChangeHandler); + this.fullscreenChangeHandler = null; + } + } +} +/** + * Base class for embedding v1 experience + * Note: The v1 version of ThoughtSpot Blink works on the AngularJS stack + * which is currently under migration to v2 + * @inheritdoc + */ +class V1Embed extends TsEmbed { + constructor(domSelector, viewConfig) { + super(domSelector, viewConfig); + /** + * Only for testing purposes. + * @hidden + */ + this.test__executeCallbacks = this.executeCallbacks; + this.viewConfig = { + excludeRuntimeFiltersfromURL: true, + excludeRuntimeParametersfromURL: true, + ...viewConfig, + }; + } + /** + * Render the app in an iframe and set up event handlers + * @param iframeSrc + */ + renderV1Embed(iframeSrc) { + return this.renderIFrame(iframeSrc); + } + getRootIframeSrc() { + const queryParams = this.getEmbedParams(); + let queryString = queryParams; + if (!this.viewConfig.excludeRuntimeParametersfromURL) { + const runtimeParameters = this.viewConfig.runtimeParameters; + const parameterQuery = getRuntimeParameters(runtimeParameters || []); + queryString = [parameterQuery, queryParams].filter(Boolean).join('&'); + } + if (!this.viewConfig.excludeRuntimeFiltersfromURL) { + const runtimeFilters = this.viewConfig.runtimeFilters; + const filterQuery = getFilterQuery(runtimeFilters || []); + queryString = [filterQuery, queryString].filter(Boolean).join('&'); + } + return this.viewConfig.enableV2Shell_experimental + ? this.getEmbedBasePath(queryString) + : this.getV1EmbedBasePath(queryString); + } + /** + * @inheritdoc + * @example + * ```js + * tsEmbed.on(EmbedEvent.Error, (data) => { + * console.error(data); + * }); + * ``` + * @example + * ```js + * tsEmbed.on(EmbedEvent.Save, (data) => { + * console.log("Answer save clicked", data); + * }, { + * start: true // This will trigger the callback on start of save + * }); + * ``` + */ + on(messageType, callback, options = { start: false }) { + const eventType = this.getCompatibleEventType(messageType); + return super.on(eventType, callback, options); + } +} + +/** + * Embed ThoughtSpot search bar + * @version SDK: 1.18.0 | ThoughtSpot: 8.10.0.cl, 9.0.1-sw + * @group Embed components + */ +let SearchBarEmbed$1 = class SearchBarEmbed extends TsEmbed { + constructor(domSelector, viewConfig) { + super(domSelector); + this.embedComponentType = 'SearchBarEmbed'; + this.viewConfig = viewConfig; + } + getEmbedParamsObject() { + const { searchOptions, dataSource, dataSources, useLastSelectedSources = false, excludeSearchTokenStringFromURL, } = this.viewConfig; + const queryParams = this.getBaseQueryParams(); + queryParams[Param.HideActions] = [...(queryParams[Param.HideActions] ?? [])]; + if (dataSources && dataSources.length) { + queryParams[Param.DataSources] = JSON.stringify(dataSources); + } + if (dataSource) { + queryParams[Param.DataSources] = `["${dataSource}"]`; + } + if (searchOptions?.searchTokenString) { + if (!excludeSearchTokenStringFromURL) { + queryParams[Param.searchTokenString] = encodeURIComponent(searchOptions.searchTokenString); + } + if (searchOptions.executeSearch) { + queryParams[Param.executeSearch] = true; + } + } + queryParams[Param.UseLastSelectedDataSource] = useLastSelectedSources; + if (dataSource || dataSources) { + queryParams[Param.UseLastSelectedDataSource] = false; + } + queryParams[Param.searchEmbed] = true; + return queryParams; + } + /** + * Construct the URL of the embedded ThoughtSpot search to be + * loaded in the iframe + * @param dataSources A list of data source GUIDs + */ + getIFrameSrc() { + const queryParams = this.getEmbedParamsObject(); + const path = 'search-bar-embed'; + let query = ''; + const queryParamsString = getQueryParamString(queryParams, true); + if (queryParamsString) { + query = `?${queryParamsString}`; + } + const tsPostHashParams = this.getThoughtSpotPostUrlParams(); + return `${this.getEmbedBasePath(query)}/embed/${path}${tsPostHashParams}`; + } + /** + * Render the embedded ThoughtSpot search + */ + async render() { + await super.render(); + const src = this.getIFrameSrc(); + await this.renderIFrame(src); + return this; + } + getSearchInitData() { + return { + searchOptions: this.viewConfig.excludeSearchTokenStringFromURL + ? this.viewConfig.searchOptions + : null, + }; + } + async getAppInitData() { + const defaultAppInitData = await super.getAppInitData(); + return { ...defaultAppInitData, ...this.getSearchInitData() }; + } +}; + +/** + * Copyright (c) 2022 + * + * Embed ThoughtSpot search or a saved answer. + * https://developers.thoughtspot.com/docs/search-embed + * @summary Search embed + * @author Ayon Ghosh + */ +/** + * Define the initial state of column custom group accordions + * in data panel v2. + */ +var DataPanelCustomColumnGroupsAccordionState$1; +(function (DataPanelCustomColumnGroupsAccordionState) { + /** + * Expand all the accordion initially in data panel v2. + */ + DataPanelCustomColumnGroupsAccordionState["EXPAND_ALL"] = "EXPAND_ALL"; + /** + * Collapse all the accordions initially in data panel v2. + */ + DataPanelCustomColumnGroupsAccordionState["COLLAPSE_ALL"] = "COLLAPSE_ALL"; + /** + * Expand the first accordion and collapse the rest. + */ + DataPanelCustomColumnGroupsAccordionState["EXPAND_FIRST"] = "EXPAND_FIRST"; +})(DataPanelCustomColumnGroupsAccordionState$1 || (DataPanelCustomColumnGroupsAccordionState$1 = {})); +const HiddenActionItemByDefaultForSearchEmbed = [ + Action.EditACopy, + Action.SaveAsView, + Action.UpdateTML, + Action.EditTML, + Action.AnswerDelete, +]; +/** + * Embed ThoughtSpot search + * @group Embed components + */ +let SearchEmbed$1 = class SearchEmbed extends TsEmbed { + constructor(domSelector, viewConfig) { + viewConfig = { + embedComponentType: 'SearchEmbed', + excludeRuntimeFiltersfromURL: true, + excludeRuntimeParametersfromURL: true, + ...viewConfig, + }; + super(domSelector, viewConfig); + } + /** + * Get the state of the data sources panel that the embedded + * ThoughtSpot search will be initialized with. + */ + getDataSourceMode() { + let dataSourceMode = DataSourceVisualMode.Expanded; + if (this.viewConfig.collapseDataSources === true + || this.viewConfig.collapseDataPanel === true) { + dataSourceMode = DataSourceVisualMode.Collapsed; + } + if (this.viewConfig.hideDataSources === true) { + dataSourceMode = DataSourceVisualMode.Hidden; + } + return dataSourceMode; + } + getSearchInitData() { + return { + ...(this.viewConfig.excludeSearchTokenStringFromURL ? { + searchOptions: { + searchTokenString: this.viewConfig.searchOptions?.searchTokenString, + }, + } : {}), + }; + } + async getAppInitData() { + const defaultAppInitData = await super.getAppInitData(); + const result = { + ...defaultAppInitData, + ...this.getSearchInitData(), + }; + if (this.viewConfig.visualOverrides) { + result.embedParams = { + ...(defaultAppInitData.embedParams || {}), + visualOverridesParams: this.viewConfig.visualOverrides, + }; + } + return result; + } + getEmbedParamsObject() { + const { hideResults, enableSearchAssist, forceTable, searchOptions, runtimeFilters, dataSource, dataSources, excludeRuntimeFiltersfromURL, hideSearchBar, dataPanelV2 = true, useLastSelectedSources = false, runtimeParameters, collapseSearchBarInitially = false, enableCustomColumnGroups = false, dataPanelCustomGroupsAccordionInitialState = DataPanelCustomColumnGroupsAccordionState$1.EXPAND_ALL, focusSearchBarOnRender = true, excludeRuntimeParametersfromURL, excludeSearchTokenStringFromURL, collapseSearchBar = true, isThisPeriodInDateFiltersEnabled, newChartsLibrary, } = this.viewConfig; + const queryParams = this.getBaseQueryParams(); + queryParams[Param.HideActions] = [ + ...(queryParams[Param.HideActions] ?? []), + ...HiddenActionItemByDefaultForSearchEmbed, + ]; + if (dataSources && dataSources.length) { + queryParams[Param.DataSources] = JSON.stringify(dataSources); + } + if (dataSource) { + queryParams[Param.DataSources] = `["${dataSource}"]`; + } + if (searchOptions?.searchTokenString) { + if (!excludeSearchTokenStringFromURL) { + queryParams[Param.searchTokenString] = encodeURIComponent(searchOptions.searchTokenString); + } + if (searchOptions.executeSearch) { + queryParams[Param.executeSearch] = true; + } + } + if (enableSearchAssist) { + queryParams[Param.EnableSearchAssist] = true; + } + if (hideResults) { + queryParams[Param.HideResult] = true; + } + if (forceTable) { + queryParams[Param.ForceTable] = true; + } + if (hideSearchBar) { + queryParams[Param.HideSearchBar] = true; + } + if (!focusSearchBarOnRender) { + queryParams[Param.FocusSearchBarOnRender] = focusSearchBarOnRender; + } + if (isThisPeriodInDateFiltersEnabled !== undefined) { + queryParams[Param.IsThisPeriodInDateFiltersEnabled] = isThisPeriodInDateFiltersEnabled; + } + if (newChartsLibrary !== undefined) { + queryParams[Param.EnableNewChartLibrary] = newChartsLibrary; + } + queryParams[Param.DataPanelV2Enabled] = dataPanelV2; + queryParams[Param.DataSourceMode] = this.getDataSourceMode(); + queryParams[Param.UseLastSelectedDataSource] = useLastSelectedSources; + if (dataSource || dataSources) { + queryParams[Param.UseLastSelectedDataSource] = false; + } + queryParams[Param.searchEmbed] = true; + queryParams[Param.CollapseSearchBarInitially] = collapseSearchBarInitially || collapseSearchBar; + queryParams[Param.EnableCustomColumnGroups] = enableCustomColumnGroups; + if (dataPanelCustomGroupsAccordionInitialState + === DataPanelCustomColumnGroupsAccordionState$1.COLLAPSE_ALL + || dataPanelCustomGroupsAccordionInitialState + === DataPanelCustomColumnGroupsAccordionState$1.EXPAND_FIRST) { + queryParams[Param.DataPanelCustomGroupsAccordionInitialState] = dataPanelCustomGroupsAccordionInitialState; + } + else { + queryParams[Param.DataPanelCustomGroupsAccordionInitialState] = DataPanelCustomColumnGroupsAccordionState$1.EXPAND_ALL; + } + return queryParams; + } + getEmbedParams() { + const { runtimeParameters, runtimeFilters, excludeRuntimeParametersfromURL, excludeRuntimeFiltersfromURL, } = this.viewConfig; + const queryParams = this.getEmbedParamsObject(); + let query = ''; + const queryParamsString = getQueryParamString(queryParams, true); + if (queryParamsString) { + query = `?${queryParamsString}`; + } + const parameterQuery = getRuntimeParameters(runtimeParameters || []); + if (parameterQuery && !excludeRuntimeParametersfromURL) + query += `&${parameterQuery}`; + const filterQuery = getFilterQuery(runtimeFilters || []); + if (filterQuery && !excludeRuntimeFiltersfromURL) { + query += `&${filterQuery}`; + } + return query; + } + /** + * Construct the URL of the embedded ThoughtSpot search to be + * loaded in the iframe + * @param answerId The GUID of a saved answer + * @param dataSources A list of data source GUIDs + */ + getIFrameSrc() { + const { answerId } = this.viewConfig; + const answerPath = answerId ? `saved-answer/${answerId}` : 'answer'; + const tsPostHashParams = this.getThoughtSpotPostUrlParams(); + return `${this.getRootIframeSrc()}/embed/${answerPath}${tsPostHashParams}`; + } + /** + * Render the embedded ThoughtSpot search + */ + async render() { + await super.render(); + const { answerId } = this.viewConfig; + const src = this.getIFrameSrc(); + await this.renderIFrame(src); + getAuthPromise().then(() => { + if (checkReleaseVersionInBeta(getReleaseVersion(), getEmbedConfig().suppressSearchEmbedBetaWarning + || getEmbedConfig().suppressErrorAlerts)) { + alert(ERROR_MESSAGE.SEARCHEMBED_BETA_WRANING_MESSAGE); + } + }); + return this; + } +}; + +/** + * Resolves enablePastConversationsSidebar with + * spotterSidebarConfig taking precedence over the + * standalone flag. + */ +const resolveEnablePastConversationsSidebar = (params) => (params.spotterSidebarConfigValue !== undefined + ? params.spotterSidebarConfigValue + : params.standaloneValue); +function buildSpotterSidebarAppInitData(defaultAppInitData, viewConfig, handleError) { + const { spotterSidebarConfig, enablePastConversationsSidebar, visualOverrides } = viewConfig; + const resolvedEnablePastConversations = resolveEnablePastConversationsSidebar({ + spotterSidebarConfigValue: spotterSidebarConfig?.enablePastConversationsSidebar, + standaloneValue: enablePastConversationsSidebar, + }); + const hasConfig = spotterSidebarConfig || resolvedEnablePastConversations !== undefined; + if (!hasConfig) { + if (visualOverrides === undefined) { + return defaultAppInitData; + } + return { + ...defaultAppInitData, + embedParams: { visualOverridesParams: visualOverrides }, + }; + } + const resolvedSidebarConfig = { + ...spotterSidebarConfig, + ...(resolvedEnablePastConversations !== undefined && { + enablePastConversationsSidebar: resolvedEnablePastConversations, + }), + }; + if (resolvedSidebarConfig.spotterDocumentationUrl !== undefined) { + const [isValid, validationError] = validateHttpUrl(resolvedSidebarConfig.spotterDocumentationUrl); + if (!isValid) { + handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.INVALID_SPOTTER_DOCUMENTATION_URL, + code: EmbedErrorCodes.INVALID_URL, + error: validationError?.message || ERROR_MESSAGE.INVALID_SPOTTER_DOCUMENTATION_URL, + }); + delete resolvedSidebarConfig.spotterDocumentationUrl; + } + } + return { + ...defaultAppInitData, + embedParams: { + ...(defaultAppInitData.embedParams || {}), + spotterSidebarConfig: resolvedSidebarConfig, + ...(visualOverrides !== undefined ? { visualOverridesParams: visualOverrides } : {}), + }, + }; +} + +function buildSpotterVizAppInitData(initData, viewConfig) { + const { spotterViz } = viewConfig; + if (!spotterViz) + return initData; + return { + ...initData, + embedParams: { + ...(initData.embedParams || {}), + spotterVizConfig: spotterViz, + }, + }; +} + +/** + * Copyright (c) 2022 + * + * Full application embedding + * https://developers.thoughtspot.com/docs/?pageid=full-embed + * @summary Full app embed + * @module + * @author Ayon Ghosh + */ +/** + * Pages within the ThoughtSpot app that can be embedded. + */ +var Page; +(function (Page) { + /** + * Home page + */ + Page["Home"] = "home"; + /** + * Search page + */ + Page["Search"] = "search"; + /** + * Saved answers listing page + */ + Page["Answers"] = "answers"; + /** + * Liveboards listing page + */ + Page["Liveboards"] = "liveboards"; + /** + * @hidden + */ + Page["Pinboards"] = "pinboards"; + /** + * Data management page + */ + Page["Data"] = "data"; + /** + * SpotIQ listing page + */ + Page["SpotIQ"] = "insights"; + /** + * Monitor Alerts Page + */ + Page["Monitor"] = "monitor"; +})(Page || (Page = {})); +/** + * Define the initial state of column custom group accordions + * in data panel v2. + */ +var DataPanelCustomColumnGroupsAccordionState; +(function (DataPanelCustomColumnGroupsAccordionState) { + /** + * Expand all the accordion initially in data panel v2. + */ + DataPanelCustomColumnGroupsAccordionState["EXPAND_ALL"] = "EXPAND_ALL"; + /** + * Collapse all the accordions initially in data panel v2. + */ + DataPanelCustomColumnGroupsAccordionState["COLLAPSE_ALL"] = "COLLAPSE_ALL"; + /** + * Expand the first accordion and collapse the rest. + */ + DataPanelCustomColumnGroupsAccordionState["EXPAND_FIRST"] = "EXPAND_FIRST"; +})(DataPanelCustomColumnGroupsAccordionState || (DataPanelCustomColumnGroupsAccordionState = {})); +var HomePageSearchBarMode; +(function (HomePageSearchBarMode) { + HomePageSearchBarMode["OBJECT_SEARCH"] = "objectSearch"; + HomePageSearchBarMode["AI_ANSWER"] = "aiAnswer"; + HomePageSearchBarMode["NONE"] = "none"; +})(HomePageSearchBarMode || (HomePageSearchBarMode = {})); +/** + * Define the version of the primary navbar + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ +var PrimaryNavbarVersion; +(function (PrimaryNavbarVersion) { + /** + * Sliding (v3) introduces a new left-side navigation hub featuring a tab switcher, + * along with updates to the top navigation bar. + * It serves as the foundational version of the PrimaryNavBar. + */ + PrimaryNavbarVersion["Sliding"] = "v3"; +})(PrimaryNavbarVersion || (PrimaryNavbarVersion = {})); +/** + * Define the version of the home page + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ +var HomePage; +(function (HomePage) { + /** + * Modular (v2) introduces the updated Modular Home Experience. + * It serves as the foundational version of the home page. + */ + HomePage["Modular"] = "v2"; + /** + * ModularWithStylingChanges (v3) introduces Modular Home Experience + * with styling changes. + */ + HomePage["ModularWithStylingChanges"] = "v3"; + /** + * Focused (v4) introduces the V4 homepage experience + * in which Watchlist and recents and incorporated together + * to form a more focused homepage. + * Pre-requisite : spotter enablement + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + */ + HomePage["Focused"] = "v4"; +})(HomePage || (HomePage = {})); +/** + * Define the version of the list page + * @version SDK: 1.40.0 | ThoughtSpot: 10.12.0.cl + */ +var ListPage; +(function (ListPage) { + /** + * List (v2) is the traditional List Experience. + * It serves as the foundational version of the list page. + */ + ListPage["List"] = "v2"; + /** + * ListWithUXChanges (v3) introduces the new updated list page with UX changes. + */ + ListPage["ListWithUXChanges"] = "v3"; +})(ListPage || (ListPage = {})); +/** + * Embeds full ThoughtSpot experience in a host application. + * @group Embed components + */ +let AppEmbed$1 = class AppEmbed extends V1Embed { + constructor(domSelector, viewConfig) { + viewConfig.embedComponentType = 'AppEmbed'; + super(domSelector, viewConfig); + this.defaultHeight = 500; + this.sendFullHeightLazyLoadData = () => { + const data = calculateVisibleElementData(this.iFrame); + // this should be fired only if the lazyLoadingForFullHeight and fullHeight are true + if (this.viewConfig.lazyLoadingForFullHeight && this.viewConfig.fullHeight) { + this.trigger(HostEvent.VisibleEmbedCoordinates, data); + } + }; + /** + * This is a handler for the RequestVisibleEmbedCoordinates event. + * It is used to send the visible coordinates data to the host application. + * @param data The event payload + * @param responder The responder function + */ + this.requestVisibleEmbedCoordinatesHandler = (data, responder) => { + logger$3.info('Sending RequestVisibleEmbedCoordinates', data); + const visibleCoordinatesData = calculateVisibleElementData(this.iFrame); + responder({ type: EmbedEvent.RequestVisibleEmbedCoordinates, data: visibleCoordinatesData }); + }; + /** + * Set the iframe height as per the computed height received + * from the ThoughtSpot app. + * @param data The event payload + */ + this.updateIFrameHeight = (data) => { + this.setIFrameHeight(Math.max(data.data, this.defaultHeight)); + this.sendFullHeightLazyLoadData(); + }; + this.embedIframeCenter = (data, responder) => { + const obj = this.getIframeCenter(); + responder({ type: EmbedEvent.EmbedIframeCenter, data: obj }); + }; + this.setIframeHeightForNonEmbedLiveboard = (data) => { + const { height: frameHeight } = this.viewConfig.frameParams || {}; + const liveboardRelatedRoutes = [ + '/pinboard/', + '/insights/pinboard/', + '/schedules/', + '/embed/viz/', + '/embed/insights/viz/', + '/liveboard/', + '/insights/liveboard/', + '/tsl-editor/PINBOARD_ANSWER_BOOK/', + '/import-tsl/PINBOARD_ANSWER_BOOK/', + ]; + if (liveboardRelatedRoutes.some((path) => data.data.currentPath.startsWith(path))) { + // Ignore the height reset of the frame, if the navigation is + // only within the liveboard page. + return; + } + this.setIFrameHeight(frameHeight || this.defaultHeight); + }; + if (this.viewConfig.fullHeight === true) { + this.on(EmbedEvent.RouteChange, this.setIframeHeightForNonEmbedLiveboard); + this.on(EmbedEvent.EmbedHeight, this.updateIFrameHeight); + this.on(EmbedEvent.EmbedIframeCenter, this.embedIframeCenter); + this.on(EmbedEvent.RequestVisibleEmbedCoordinates, this.requestVisibleEmbedCoordinatesHandler); + } + } + /** + * Extends the default APP_INIT payload with `embedParams.spotterSidebarConfig` + * so the conv-assist app can read sidebar configuration on initialisation. + * + * Precedence for `enablePastConversationsSidebar`: + * `spotterSidebarConfig.enablePastConversationsSidebar` wins over the + * deprecated top-level `enablePastConversationsSidebar` flag; if the former + * is absent the latter is used as a fallback. + * + * An invalid `spotterDocumentationUrl` triggers a validation error and is + * excluded from the payload rather than forwarded to the app. + */ + async getAppInitData() { + const defaultAppInitData = await super.getAppInitData(); + const sidebarInitData = buildSpotterSidebarAppInitData(defaultAppInitData, this.viewConfig, this.handleError.bind(this)); + return buildSpotterVizAppInitData(sidebarInitData, this.viewConfig); + } + /** + * Constructs a map of parameters to be passed on to the + * embedded Liveboard or visualization. + */ + getEmbedParams() { + const { tag, hideTagFilterChips, hideObjects, liveboardV2, showPrimaryNavbar, disableProfileAndHelp, hideHamburger, hideObjectSearch, hideNotification, hideApplicationSwitcher, hideOrgSwitcher, enableSearchAssist, newConnectionsExperience, fullHeight, dataPanelV2 = true, hideLiveboardHeader = false, showLiveboardTitle = true, showLiveboardDescription = true, showMaskedFilterChip = false, isLiveboardMasterpiecesEnabled = false, newChartsLibrary, hideHomepageLeftNav = false, modularHomeExperience = false, isLiveboardHeaderSticky = true, enableAskSage, collapseSearchBarInitially = false, enable2ColumnLayout, enableCustomColumnGroups = false, dataPanelCustomGroupsAccordionInitialState = DataPanelCustomColumnGroupsAccordionState.EXPAND_ALL, collapseSearchBar = true, isLiveboardCompactHeaderEnabled = false, showLiveboardVerifiedBadge = true, showLiveboardReverifyBanner = true, hideIrrelevantChipsInLiveboardTabs = false, isEnhancedFilterInteractivityEnabled = false, homePageSearchBarMode, isUnifiedSearchExperienceEnabled = true, enablePendoHelp = true, discoveryExperience, coverAndFilterOptionInPDF = false, isLiveboardStylingAndGroupingEnabled, isPNGInScheduledEmailsEnabled = false, isLiveboardXLSXCSVDownloadEnabled = false, isGranularXLSXCSVSchedulesEnabled = false, isCentralizedLiveboardFilterUXEnabled = false, isLinkParametersEnabled, updatedSpotterChatPrompt, enableStopAnswerGenerationEmbed, spotterChatConfig, minimumHeight, isThisPeriodInDateFiltersEnabled, enableHomepageAnnouncement = false, isContinuousLiveboardPDFEnabled = false, enableLiveboardDataCache, } = this.viewConfig; + let params = {}; + params[Param.PrimaryNavHidden] = !showPrimaryNavbar; + params[Param.HideProfleAndHelp] = !!disableProfileAndHelp; + params[Param.HideApplicationSwitcher] = !!hideApplicationSwitcher; + params[Param.HideOrgSwitcher] = !!hideOrgSwitcher; + params[Param.HideLiveboardHeader] = hideLiveboardHeader; + params[Param.ShowLiveboardTitle] = showLiveboardTitle; + params[Param.ShowLiveboardDescription] = !!showLiveboardDescription; + params[Param.ShowMaskedFilterChip] = showMaskedFilterChip; + params[Param.IsLiveboardMasterpiecesEnabled] = isLiveboardMasterpiecesEnabled; + if (newChartsLibrary !== undefined) { + params[Param.EnableNewChartLibrary] = newChartsLibrary; + } + params[Param.LiveboardHeaderSticky] = isLiveboardHeaderSticky; + params[Param.IsFullAppEmbed] = true; + params[Param.LiveboardHeaderV2] = isLiveboardCompactHeaderEnabled; + params[Param.IsEnhancedFilterInteractivityEnabled] = isEnhancedFilterInteractivityEnabled; + params[Param.ShowLiveboardVerifiedBadge] = showLiveboardVerifiedBadge; + params[Param.ShowLiveboardReverifyBanner] = showLiveboardReverifyBanner; + params[Param.HideIrrelevantFiltersInTab] = hideIrrelevantChipsInLiveboardTabs; + if (isUnifiedSearchExperienceEnabled !== undefined) { + params[Param.IsUnifiedSearchExperienceEnabled] = isUnifiedSearchExperienceEnabled; + } + params[Param.CoverAndFilterOptionInPDF] = !!coverAndFilterOptionInPDF; + params = this.getBaseQueryParams(params); + if (!isUndefined(updatedSpotterChatPrompt)) { + params[Param.UpdatedSpotterChatPrompt] = !!updatedSpotterChatPrompt; + } + if (!isUndefined(enableStopAnswerGenerationEmbed)) { + params[Param.EnableStopAnswerGenerationEmbed] = !!enableStopAnswerGenerationEmbed; + } + // Handle spotterChatConfig params + if (spotterChatConfig) { + const { hideToolResponseCardBranding, toolResponseCardBrandingLabel, spotterFileUploadEnabled, spotterFileUploadFileTypes, } = spotterChatConfig; + setParamIfDefined(params, Param.HideToolResponseCardBranding, hideToolResponseCardBranding, true); + setParamIfDefined(params, Param.ToolResponseCardBrandingLabel, toolResponseCardBrandingLabel); + if (spotterFileUploadEnabled !== undefined) { + params[Param.SpotterFileUploadEnabled] = spotterFileUploadEnabled; + } + if (spotterFileUploadFileTypes !== undefined) { + params[Param.SpotterFileUploadFileTypes] = JSON.stringify(spotterFileUploadFileTypes); + } + } + if (hideObjectSearch) { + params[Param.HideObjectSearch] = !!hideObjectSearch; + } + if (hideHamburger) { + params[Param.HideHamburger] = !!hideHamburger; + } + if (hideNotification) { + params[Param.HideNotification] = !!hideNotification; + } + if (fullHeight === true) { + params[Param.fullHeight] = true; + if (this.viewConfig.lazyLoadingForFullHeight) { + params[Param.IsLazyLoadingForEmbedEnabled] = true; + if (isValidCssMargin(this.viewConfig.lazyLoadingMargin)) { + params[Param.RootMarginForLazyLoad] = this.viewConfig.lazyLoadingMargin; + } + } + } + if (tag) { + params[Param.Tag] = tag; + } + if (hideObjects && hideObjects.length) { + params[Param.HideObjects] = JSON.stringify(hideObjects); + } + if (liveboardV2 !== undefined) { + params[Param.LiveboardV2Enabled] = liveboardV2; + } + if (enableSearchAssist !== undefined) { + params[Param.EnableSearchAssist] = enableSearchAssist; + } + if (newConnectionsExperience !== undefined) { + params[Param.EnableConnectionNewExperience] = newConnectionsExperience; + } + if (enable2ColumnLayout !== undefined) { + params[Param.Enable2ColumnLayout] = enable2ColumnLayout; + } + if (enableAskSage) { + params[Param.enableAskSage] = enableAskSage; + } + if (homePageSearchBarMode) { + params[Param.HomePageSearchBarMode] = homePageSearchBarMode; + } + if (enablePendoHelp !== undefined) { + params[Param.EnablePendoHelp] = enablePendoHelp; + } + if (isLiveboardStylingAndGroupingEnabled !== undefined) { + params[Param.IsLiveboardStylingAndGroupingEnabled] = isLiveboardStylingAndGroupingEnabled; + } + if (isPNGInScheduledEmailsEnabled !== undefined) { + params[Param.isPNGInScheduledEmailsEnabled] = isPNGInScheduledEmailsEnabled; + } + if (isLiveboardXLSXCSVDownloadEnabled !== undefined) { + params[Param.isLiveboardXLSXCSVDownloadEnabled] = isLiveboardXLSXCSVDownloadEnabled; + } + if (isGranularXLSXCSVSchedulesEnabled !== undefined) { + params[Param.isGranularXLSXCSVSchedulesEnabled] = isGranularXLSXCSVSchedulesEnabled; + } + if (hideTagFilterChips !== undefined) { + params[Param.HideTagFilterChips] = hideTagFilterChips; + } + if (isLinkParametersEnabled !== undefined) { + params[Param.isLinkParametersEnabled] = isLinkParametersEnabled; + } + if (isCentralizedLiveboardFilterUXEnabled != undefined) { + params[Param.isCentralizedLiveboardFilterUXEnabled] = isCentralizedLiveboardFilterUXEnabled; + } + if (isThisPeriodInDateFiltersEnabled !== undefined) { + params[Param.IsThisPeriodInDateFiltersEnabled] = isThisPeriodInDateFiltersEnabled; + } + if (enableHomepageAnnouncement !== undefined) { + params[Param.EnableHomepageAnnouncement] = enableHomepageAnnouncement; + } + if (isContinuousLiveboardPDFEnabled !== undefined) { + params[Param.IsWYSIWYGLiveboardPDFEnabled] = isContinuousLiveboardPDFEnabled; + } + this.defaultHeight = minimumHeight || this.defaultHeight; + if (enableLiveboardDataCache !== undefined) { + params[Param.EnableLiveboardDataCache] = enableLiveboardDataCache; + } + params[Param.DataPanelV2Enabled] = dataPanelV2; + params[Param.HideHomepageLeftNav] = hideHomepageLeftNav; + params[Param.ModularHomeExperienceEnabled] = modularHomeExperience; + params[Param.CollapseSearchBarInitially] = collapseSearchBarInitially || collapseSearchBar; + params[Param.EnableCustomColumnGroups] = enableCustomColumnGroups; + if (dataPanelCustomGroupsAccordionInitialState + === DataPanelCustomColumnGroupsAccordionState.COLLAPSE_ALL + || dataPanelCustomGroupsAccordionInitialState + === DataPanelCustomColumnGroupsAccordionState.EXPAND_FIRST) { + params[Param.DataPanelCustomGroupsAccordionInitialState] = dataPanelCustomGroupsAccordionInitialState; + } + else { + params[Param.DataPanelCustomGroupsAccordionInitialState] = DataPanelCustomColumnGroupsAccordionState.EXPAND_ALL; + } + if (modularHomeExperience !== undefined) { + params[Param.ModularHomeExperienceEnabled] = modularHomeExperience; + } + // Set navigation to v2 by default to avoid problems like the app + // switcher (9-dot menu) not showing when v3 navigation is turned on + // at the cluster level. + // To use v3 navigation, we must manually set the discoveryExperience + // settings. + params[Param.NavigationVersion] = 'v2'; + // Set homePageVersion to v2 by default to reset the LD flag value + // for the homepageVersion. + params[Param.HomepageVersion] = 'v2'; + if (discoveryExperience) { + // primaryNavbarVersion v3 will enabled the new left navigation + if (discoveryExperience.primaryNavbarVersion === PrimaryNavbarVersion.Sliding) { + params[Param.NavigationVersion] = discoveryExperience.primaryNavbarVersion; + // Enable the modularHomeExperience when Sliding is enabled. + params[Param.ModularHomeExperienceEnabled] = true; + } + // homePage v2 will enable the modular home page + // and it will override the modularHomeExperience value + if (discoveryExperience.homePage === HomePage.Modular) { + params[Param.ModularHomeExperienceEnabled] = true; + } + // ModularWithStylingChanges (v3) introduces the styling changes + // to the Modular Homepage. + // v3 will be the base version of homePageVersion. + if (discoveryExperience.homePage === HomePage.ModularWithStylingChanges) { + params[Param.HomepageVersion] = HomePage.ModularWithStylingChanges; + } + // listPageVersion can be changed to v2 or v3 + if (discoveryExperience.listPageVersion !== undefined) { + params[Param.ListPageVersion] = discoveryExperience.listPageVersion; + } + if (discoveryExperience.homePage === HomePage.Focused) { + params[Param.HomepageVersion] = HomePage.Focused; + } + } + const queryParams = getQueryParamString(params, true); + return queryParams; + } + /** + * Constructs the URL of the ThoughtSpot app page to be rendered. + * @param pageId The ID of the page to be embedded. + */ + getIFrameSrc() { + const { pageId, path, modularHomeExperience } = this.viewConfig; + const pageRoute = this.formatPath(path) || this.getPageRoute(pageId, modularHomeExperience); + let url = `${this.getRootIframeSrc()}/${pageRoute}`; + const tsPostHashParams = this.getThoughtSpotPostUrlParams(); + url = `${url}${tsPostHashParams}`; + return url; + } + /** + * Gets the ThoughtSpot route of the page for a particular page ID. + * @param pageId The identifier for a page in the ThoughtSpot app. + * @param modularHomeExperience + */ + getPageRoute(pageId, modularHomeExperience = false) { + switch (pageId) { + case Page.Search: + return 'answer'; + case Page.Answers: + return modularHomeExperience ? 'home/answers' : 'answers'; + case Page.Liveboards: + return modularHomeExperience ? 'home/liveboards' : 'pinboards'; + case Page.Pinboards: + return modularHomeExperience ? 'home/liveboards' : 'pinboards'; + case Page.Data: + return 'data/tables'; + case Page.SpotIQ: + return modularHomeExperience ? 'home/spotiq-analysis' : 'insights/results'; + case Page.Monitor: + return modularHomeExperience ? 'home/monitor-alerts' : 'insights/monitor-alerts'; + case Page.Home: + default: + return 'home'; + } + } + /** + * Formats the path provided by the user. + * @param path The URL path. + * @returns The URL path that the embedded app understands. + */ + formatPath(path) { + if (!path) { + return null; + } + // remove leading slash + if (path.indexOf('/') === 0) { + return path.substring(1); + } + return path; + } + /** + * Navigate to particular page for app embed. eg:answers/pinboards/home + * This is used for embedding answers, pinboards, visualizations and full application + * only. + * @param path string | number The string, set to iframe src and navigate to new page + * eg: appEmbed.navigateToPage('pinboards') + * When used with `noReload` (default: true) this can also be a number + * like 1/-1 to go forward/back. + * @param noReload boolean Trigger the navigation without reloading the page + * @version SDK: 1.12.0 | ThoughtSpot: 8.4.0.cl, 8.4.1-sw + */ + navigateToPage(path, noReload = false) { + if (!this.iFrame) { + logger$3.log('Please call render before invoking this method'); + return; + } + if (noReload) { + this.trigger(HostEvent.Navigate, path); + } + else { + if (typeof path !== 'string') { + logger$3.warn('Path can only by a string when triggered without noReload'); + return; + } + const iframeSrc = this.iFrame.src; + const embedPath = '#/embed'; + const currentPath = iframeSrc.includes(embedPath) ? embedPath : '#'; + this.iFrame.src = `${iframeSrc.split(currentPath)[0]}${currentPath}/${path.replace(/^\/?#?\//, '')}`; + } + } + /** + * Destroys the ThoughtSpot embed, and remove any nodes from the DOM. + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + destroy() { + super.destroy(); + this.unregisterLazyLoadEvents(); + } + postRender() { + this.registerLazyLoadEvents(); + } + registerLazyLoadEvents() { + if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) { + // TODO: Use passive: true, install modernizr to check for passive + window.addEventListener('resize', this.sendFullHeightLazyLoadData); + window.addEventListener('scroll', this.sendFullHeightLazyLoadData, true); + } + } + unregisterLazyLoadEvents() { + if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) { + window.removeEventListener('resize', this.sendFullHeightLazyLoadData); + window.removeEventListener('scroll', this.sendFullHeightLazyLoadData); + } + } + /** + * Renders the embedded application pages in the ThoughtSpot app. + * @param renderOptions An object containing the page ID + * to be embedded. + */ + async render() { + await super.render(); + const src = this.getIFrameSrc(); + await this.renderV1Embed(src); + this.postRender(); + return this; + } +}; + +const getPreviewQuery = ` +query GetEurekaVizSnapshots( + $vizId: String!, $liveboardId: String!) { + getEurekaVizSnapshot( + id: $vizId + reportBookId: $liveboardId + reportBookType: "PINBOARD_ANSWER_BOOK" + version: 9999999 + ) { + id + vizContent + snapshotType + createdMs + } + } +`; +/** + * + * @param thoughtSpotHost + * @param vizId + * @param liveboardId + */ +async function getPreview(thoughtSpotHost, vizId, liveboardId) { + return graphqlQuery({ + query: getPreviewQuery, + variables: { vizId, liveboardId }, + thoughtSpotHost, + }); +} + +const addPreviewStylesIfNotPresent = () => { + const styleEl = document.getElementById('ts-preview-style'); + if (styleEl) { + return; + } + const previewStyles = ` + + + `; + document.head.insertAdjacentHTML('beforeend', previewStyles); +}; + +/** + * Copyright (c) 2022 + * + * Embed a ThoughtSpot Liveboard or visualization + * https://developers.thoughtspot.com/docs/embed-liveboard + * https://developers.thoughtspot.com/docs/embed-a-viz + * @summary Liveboard & visualization embed + * @author Ayon Ghosh + */ +/** + * Embed a ThoughtSpot Liveboard or visualization. When rendered it already + * waits for the authentication to complete, so you need not wait for + * `AuthStatus.SUCCESS`. + * @group Embed components + * @example + * ```js + * import { .. } from '@thoughtspot/visual-embed-sdk'; + * init({ ... }); + * const embed = new LiveboardEmbed("#container", { + * liveboardId: , + * // .. other params here. + * }) + * ``` + */ +let LiveboardEmbed$1 = class LiveboardEmbed extends V1Embed { + constructor(domSelector, viewConfig) { + viewConfig.embedComponentType = 'LiveboardEmbed'; + super(domSelector, viewConfig); + this.defaultHeight = 500; + this.sendFullHeightLazyLoadData = () => { + const data = calculateVisibleElementData(this.iFrame); + // this should be fired only if the lazyLoadingForFullHeight and fullHeight are true + if (this.viewConfig.lazyLoadingForFullHeight && this.viewConfig.fullHeight) { + this.trigger(HostEvent.VisibleEmbedCoordinates, data); + } + }; + /** + * This is a handler for the RequestVisibleEmbedCoordinates event. + * It is used to send the visible coordinates data to the host application. + * @param data The event payload + * @param responder The responder function + */ + this.requestVisibleEmbedCoordinatesHandler = (data, responder) => { + logger$3.info('Sending RequestVisibleEmbedCoordinates', data); + const visibleCoordinatesData = calculateVisibleElementData(this.iFrame); + responder({ type: EmbedEvent.RequestVisibleEmbedCoordinates, data: visibleCoordinatesData }); + }; + /** + * Set the iframe height as per the computed height received + * from the ThoughtSpot app. + * @param data The event payload + */ + this.updateIFrameHeight = (data) => { + this.setIFrameHeight(Math.max(data.data, this.defaultHeight)); + this.sendFullHeightLazyLoadData(); + }; + this.embedIframeCenter = (data, responder) => { + const obj = this.getIframeCenter(); + responder({ type: EmbedEvent.EmbedIframeCenter, data: obj }); + }; + this.setIframeHeightForNonEmbedLiveboard = (data) => { + const { height: frameHeight } = this.viewConfig.frameParams || {}; + const liveboardRelatedRoutes = [ + '/pinboard/', + '/insights/pinboard/', + '/schedules/', + '/embed/viz/', + '/embed/insights/viz/', + '/liveboard/', + '/insights/liveboard/', + '/tsl-editor/PINBOARD_ANSWER_BOOK/', + '/import-tsl/PINBOARD_ANSWER_BOOK/', + ]; + if (liveboardRelatedRoutes.some((path) => data.data.currentPath.startsWith(path))) { + // Ignore the height reset of the frame, if the navigation is + // only within the liveboard page. + return; + } + this.setIFrameHeight(frameHeight || this.defaultHeight); + }; + /** + * @hidden + * Internal state to track the current liveboard id. + * This is used to navigate to the correct liveboard when the prerender is visible. + */ + this.currentLiveboardState = { + liveboardId: this.viewConfig.liveboardId, + vizId: this.viewConfig.vizId, + activeTabId: this.viewConfig.activeTabId, + personalizedViewId: this.viewConfig.personalizedViewId, + }; + if (this.viewConfig.fullHeight === true) { + if (this.viewConfig.vizId) { + logger$3.warn('Full height is currently only supported for Liveboard embeds.' + + 'Using full height with vizId might lead to unexpected behavior.'); + } + this.on(EmbedEvent.RouteChange, this.setIframeHeightForNonEmbedLiveboard); + this.on(EmbedEvent.EmbedHeight, this.updateIFrameHeight); + this.on(EmbedEvent.EmbedIframeCenter, this.embedIframeCenter); + this.on(EmbedEvent.RequestVisibleEmbedCoordinates, this.requestVisibleEmbedCoordinatesHandler); + } + } + async getAppInitData() { + const defaultAppInitData = await super.getAppInitData(); + return buildSpotterVizAppInitData(defaultAppInitData, this.viewConfig); + } + /** + * Construct a map of params to be passed on to the + * embedded Liveboard or visualization. + */ + getEmbedParams() { + const params = this.getEmbedParamsObject(); + const queryParams = getQueryParamString(params, true); + return queryParams; + } + getEmbedParamsObject() { + let params = {}; + params = this.getBaseQueryParams(params); + const { enableVizTransformations, fullHeight, defaultHeight, minimumHeight, visibleVizs, liveboardV2, vizId, hideTabPanel, activeTabId, hideLiveboardHeader, showLiveboardDescription, showLiveboardTitle, isLiveboardHeaderSticky = true, isLiveboardCompactHeaderEnabled = false, showLiveboardVerifiedBadge = true, showLiveboardReverifyBanner = true, hideIrrelevantChipsInLiveboardTabs = false, showMaskedFilterChip = false, isLiveboardMasterpiecesEnabled = false, newChartsLibrary, isEnhancedFilterInteractivityEnabled = false, enableAskSage, enable2ColumnLayout, dataPanelV2 = true, enableCustomColumnGroups = false, oAuthPollingInterval, isForceRedirect, dataSourceId, coverAndFilterOptionInPDF = false, isLiveboardStylingAndGroupingEnabled, isPNGInScheduledEmailsEnabled = false, isLiveboardXLSXCSVDownloadEnabled = false, isGranularXLSXCSVSchedulesEnabled = false, showSpotterLimitations, isCentralizedLiveboardFilterUXEnabled = false, isLinkParametersEnabled, updatedSpotterChatPrompt, enableStopAnswerGenerationEmbed, spotterChatConfig, isThisPeriodInDateFiltersEnabled, isContinuousLiveboardPDFEnabled = false, enableLiveboardDataCache, } = this.viewConfig; + const preventLiveboardFilterRemoval = this.viewConfig.preventLiveboardFilterRemoval + || this.viewConfig.preventPinboardFilterRemoval; + if (fullHeight === true) { + params[Param.fullHeight] = true; + if (this.viewConfig.lazyLoadingForFullHeight) { + params[Param.IsLazyLoadingForEmbedEnabled] = true; + if (isValidCssMargin(this.viewConfig.lazyLoadingMargin)) { + params[Param.RootMarginForLazyLoad] = this.viewConfig.lazyLoadingMargin; + } + } + } + this.defaultHeight = minimumHeight || defaultHeight || this.defaultHeight; + if (enableVizTransformations !== undefined) { + params[Param.EnableVizTransformations] = enableVizTransformations.toString(); + } + if (preventLiveboardFilterRemoval) { + params[Param.preventLiveboardFilterRemoval] = true; + } + if (!isUndefined(updatedSpotterChatPrompt)) { + params[Param.UpdatedSpotterChatPrompt] = !!updatedSpotterChatPrompt; + } + if (!isUndefined(enableStopAnswerGenerationEmbed)) { + params[Param.EnableStopAnswerGenerationEmbed] = !!enableStopAnswerGenerationEmbed; + } + if (visibleVizs) { + params[Param.visibleVizs] = visibleVizs; + } + params[Param.livedBoardEmbed] = true; + if (vizId) { + params[Param.vizEmbed] = true; + } + if (liveboardV2 !== undefined) { + params[Param.LiveboardV2Enabled] = liveboardV2; + } + if (enable2ColumnLayout !== undefined) { + params[Param.Enable2ColumnLayout] = enable2ColumnLayout; + } + if (hideTabPanel) { + params[Param.HideTabPanel] = hideTabPanel; + } + if (hideLiveboardHeader) { + params[Param.HideLiveboardHeader] = hideLiveboardHeader; + } + if (showLiveboardDescription) { + params[Param.ShowLiveboardDescription] = showLiveboardDescription; + } + if (showLiveboardTitle) { + params[Param.ShowLiveboardTitle] = showLiveboardTitle; + } + if (enableAskSage) { + params[Param.enableAskSage] = enableAskSage; + } + if (oAuthPollingInterval !== undefined) { + params[Param.OauthPollingInterval] = oAuthPollingInterval; + } + if (isForceRedirect) { + params[Param.IsForceRedirect] = isForceRedirect; + } + if (dataSourceId !== undefined) { + params[Param.DataSourceId] = dataSourceId; + } + if (isLiveboardStylingAndGroupingEnabled !== undefined) { + params[Param.IsLiveboardStylingAndGroupingEnabled] = isLiveboardStylingAndGroupingEnabled; + } + if (isPNGInScheduledEmailsEnabled !== undefined) { + params[Param.isPNGInScheduledEmailsEnabled] = isPNGInScheduledEmailsEnabled; + } + if (isLiveboardXLSXCSVDownloadEnabled !== undefined) { + params[Param.isLiveboardXLSXCSVDownloadEnabled] = isLiveboardXLSXCSVDownloadEnabled; + } + if (isGranularXLSXCSVSchedulesEnabled !== undefined) { + params[Param.isGranularXLSXCSVSchedulesEnabled] = isGranularXLSXCSVSchedulesEnabled; + } + if (showSpotterLimitations !== undefined) { + params[Param.ShowSpotterLimitations] = showSpotterLimitations; + } + // Handle spotterChatConfig params + if (spotterChatConfig) { + const { hideToolResponseCardBranding, toolResponseCardBrandingLabel, spotterFileUploadEnabled, spotterFileUploadFileTypes, } = spotterChatConfig; + setParamIfDefined(params, Param.HideToolResponseCardBranding, hideToolResponseCardBranding, true); + setParamIfDefined(params, Param.ToolResponseCardBrandingLabel, toolResponseCardBrandingLabel); + if (spotterFileUploadEnabled !== undefined) { + params[Param.SpotterFileUploadEnabled] = spotterFileUploadEnabled; + } + if (spotterFileUploadFileTypes !== undefined) { + params[Param.SpotterFileUploadFileTypes] = JSON.stringify(spotterFileUploadFileTypes); + } + } + if (isLinkParametersEnabled !== undefined) { + params[Param.isLinkParametersEnabled] = isLinkParametersEnabled; + } + if (isCentralizedLiveboardFilterUXEnabled !== undefined) { + params[Param.isCentralizedLiveboardFilterUXEnabled] = isCentralizedLiveboardFilterUXEnabled; + } + if (isThisPeriodInDateFiltersEnabled !== undefined) { + params[Param.IsThisPeriodInDateFiltersEnabled] = isThisPeriodInDateFiltersEnabled; + } + if (isContinuousLiveboardPDFEnabled !== undefined) { + params[Param.IsWYSIWYGLiveboardPDFEnabled] = isContinuousLiveboardPDFEnabled; + } + if (enableLiveboardDataCache !== undefined) { + params[Param.EnableLiveboardDataCache] = enableLiveboardDataCache; + } + if (newChartsLibrary !== undefined) { + params[Param.EnableNewChartLibrary] = newChartsLibrary; + } + params[Param.LiveboardHeaderSticky] = isLiveboardHeaderSticky; + params[Param.LiveboardHeaderV2] = isLiveboardCompactHeaderEnabled; + params[Param.ShowLiveboardVerifiedBadge] = showLiveboardVerifiedBadge; + params[Param.ShowLiveboardReverifyBanner] = showLiveboardReverifyBanner; + params[Param.HideIrrelevantFiltersInTab] = hideIrrelevantChipsInLiveboardTabs; + params[Param.ShowMaskedFilterChip] = showMaskedFilterChip; + params[Param.IsLiveboardMasterpiecesEnabled] = isLiveboardMasterpiecesEnabled; + params[Param.IsEnhancedFilterInteractivityEnabled] = isEnhancedFilterInteractivityEnabled; + params[Param.DataPanelV2Enabled] = dataPanelV2; + params[Param.EnableCustomColumnGroups] = enableCustomColumnGroups; + params[Param.CoverAndFilterOptionInPDF] = coverAndFilterOptionInPDF; + getQueryParamString(params, true); + return params; + } + getIframeSuffixSrc(liveboardId, vizId, activeTabId, personalizedViewId) { + // Extract view from liveboardId if passed along with it (legacy + // approach) + // View must be appended as query param at the end, not + // embedded in path + let liveboardGuid = liveboardId; + let legacyViewId; + if (liveboardId?.includes('?')) { + const [id, query] = liveboardId.split('?'); + liveboardGuid = id; + const params = new URLSearchParams(query); + legacyViewId = params.get('view') || undefined; + } + // personalizedViewId takes precedence over legacyViewId (when passed + // as part of liveboardId) + const effectiveViewId = personalizedViewId || legacyViewId; + let suffix = `/embed/viz/${liveboardGuid}`; + if (activeTabId) { + suffix = `${suffix}/tab/${activeTabId}`; + } + if (vizId) { + suffix = `${suffix}/${vizId}`; + } + const additionalParams = {}; + if (effectiveViewId) { + additionalParams.view = effectiveViewId; + } + const tsPostHashParams = this.getThoughtSpotPostUrlParams(additionalParams); + suffix = `${suffix}${tsPostHashParams}`; + return suffix; + } + /** + * Construct the URL of the embedded ThoughtSpot Liveboard or visualization + * to be loaded within the iFrame. + */ + getIFrameSrc() { + const { vizId, activeTabId, personalizedViewId } = this.viewConfig; + const liveboardId = this.viewConfig.liveboardId ?? this.viewConfig.pinboardId; + if (!liveboardId) { + this.handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.LIVEBOARD_VIZ_ID_VALIDATION, + code: EmbedErrorCodes.LIVEBOARD_ID_MISSING, + error: ERROR_MESSAGE.LIVEBOARD_VIZ_ID_VALIDATION, + }); + } + return `${this.getRootIframeSrc()}${this.getIframeSuffixSrc(liveboardId, vizId, activeTabId, personalizedViewId)}`; + } + setActiveTab(data) { + if (!this.viewConfig.vizId) { + const prefixPath = this.iFrame.src.split('#/')[1].split('/tab')[0]; + const path = `${prefixPath}/tab/${data.tabId}`; + super.trigger(HostEvent.Navigate, path); + } + } + async showPreviewLoader() { + if (!this.viewConfig.showPreviewLoader || !this.viewConfig.vizId) { + return; + } + try { + const preview = await getPreview(this.thoughtSpotHost, this.viewConfig.vizId, this.viewConfig.liveboardId); + if (!preview.vizContent) { + return; + } + addPreviewStylesIfNotPresent(); + const div = document.createElement('div'); + div.innerHTML = ` +
+ ${preview.vizContent} +
+ `; + const previewDiv = div.firstElementChild; + this.hostElement.appendChild(previewDiv); + this.hostElement.style.position = 'relative'; + this.on(EmbedEvent.Data, () => { + previewDiv.remove(); + }); + } + catch (error) { + console.error('Error fetching preview', error); + } + } + beforePrerenderVisible() { + super.beforePrerenderVisible(); + const embedObj = this.getPreRenderObj(); + this.executeAfterEmbedContainerLoaded(() => { + this.navigateToLiveboard(this.viewConfig.liveboardId, this.viewConfig.vizId, this.viewConfig.activeTabId, this.viewConfig.personalizedViewId); + if (embedObj) { + embedObj.currentLiveboardState = { + liveboardId: this.viewConfig.liveboardId, + vizId: this.viewConfig.vizId, + activeTabId: this.viewConfig.activeTabId, + personalizedViewId: this.viewConfig.personalizedViewId, + }; + } + }); + } + async handleRenderForPrerender() { + if (isUndefined(this.viewConfig.liveboardId)) { + return this.prerenderGeneric(); + } + return super.handleRenderForPrerender(); + } + /** + * Triggers an event to the embedded app + * @param {HostEvent} messageType The event type + * @param {any} data The payload to send with the message + * @returns A promise that resolves with the response from the embedded app + */ + trigger(messageType, data = {}, context) { + const dataWithVizId = data; + if (messageType === HostEvent.SetActiveTab) { + this.setActiveTab(data); + return Promise.resolve(null); + } + if (typeof dataWithVizId === 'object' && this.viewConfig.vizId) { + dataWithVizId.vizId = this.viewConfig.vizId; + } + return super.trigger(messageType, dataWithVizId, context); + } + /** + * Destroys the ThoughtSpot embed, and remove any nodes from the DOM. + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + destroy() { + super.destroy(); + this.unregisterLazyLoadEvents(); + } + postRender() { + this.registerLazyLoadEvents(); + } + registerLazyLoadEvents() { + if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) { + // TODO: Use passive: true, install modernizr to check for passive + window.addEventListener('resize', this.sendFullHeightLazyLoadData); + window.addEventListener('scroll', this.sendFullHeightLazyLoadData, true); + } + } + unregisterLazyLoadEvents() { + if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) { + window.removeEventListener('resize', this.sendFullHeightLazyLoadData); + window.removeEventListener('scroll', this.sendFullHeightLazyLoadData); + } + } + /** + * Render an embedded ThoughtSpot Liveboard or visualization + * @param renderOptions An object specifying the Liveboard ID, + * visualization ID and the runtime filters. + */ + async render() { + await super.render(); + const src = this.getIFrameSrc(); + await this.renderV1Embed(src); + this.showPreviewLoader(); + this.postRender(); + return this; + } + navigateToLiveboard(liveboardId, vizId, activeTabId, personalizedViewId) { + const path = this.getIframeSuffixSrc(liveboardId, vizId, activeTabId, personalizedViewId); + this.viewConfig.liveboardId = liveboardId; + this.viewConfig.activeTabId = activeTabId; + this.viewConfig.vizId = vizId; + this.viewConfig.personalizedViewId = personalizedViewId; + if (this.isRendered) { + this.trigger(HostEvent.Navigate, path.substring(1)); + } + else if (this.viewConfig.preRenderId) { + this.preRender(true); + } + else { + this.render(); + } + } + /** + * Returns the full url of the Liveboard/visualization which can be used to open + * this Liveboard inside the full ThoughtSpot application in a new tab. + * @returns url string + */ + getLiveboardUrl() { + let url = `${this.thoughtSpotHost}/#/pinboard/${this.viewConfig.liveboardId}`; + if (this.viewConfig.activeTabId) { + url = `${url}/tab/${this.viewConfig.activeTabId}`; + } + if (this.viewConfig.vizId) { + url = `${url}/${this.viewConfig.vizId}`; + } + if (this.viewConfig.personalizedViewId) { + url = `${url}?view=${this.viewConfig.personalizedViewId}`; + } + return url; + } +}; + +const createConversation = ` +mutation CreateConversation($params: Input_convassist_CreateConversationRequest) { + ConvAssist__createConversation(request: $params) { + convId + initialCtx { + type + tsAnsCtx { + sessionId + genNo + stateKey { + transactionId + generationNumber + } + worksheet { + worksheetId + worksheetName + } + } + } + } +} +`; +const sendMessage = ` +query SendMessage($params: Input_convassist_SendMessageRequest) { + ConvAssist__sendMessage(request: $params) { + responses { + timestamp + msgId + data { + asstRespData { + tool + asstRespText + nlsAnsData { + sageQuerySuggestions { + sageQueryTokens { + additions { + phrase { + isCompletePhrase + numTokens + phraseType + startIndex + __typename + } + tokens { + token + dataType + typeEnum + guid + tokenMetadata { + name + __typename + } + __typename + } + __typename + } + phrases { + isCompletePhrase + numTokens + phraseType + startIndex + __typename + } + removals { + phrase { + isCompletePhrase + numTokens + phraseType + startIndex + __typename + } + tokens { + token + dataType + typeEnum + guid + tokenMetadata { + name + __typename + } + __typename + } + __typename + } + tokens { + token + dataType + typeEnum + guid + tokenMetadata { + name + __typename + } + __typename + } + __typename + } + llmReasoning { + assumptions + clarifications + interpretation + __typename + } + tokens + tmlTokens + worksheetId + tokens + description + title + tmlTokens + cached + sqlQuery + sessionId + genNo + formulaInfo { + name + expression + __typename + } + tmlPhrases + ambiguousPhrases { + alternativePhrases { + phraseType + token { + token + dataType + typeEnum + guid + tokenMetadata { + name + __typename + } + __typename + } + __typename + } + ambiguityType + token { + token + dataType + typeEnum + guid + tokenMetadata { + name + __typename + } + __typename + } + __typename + } + ambiguousTokens { + alternativeTokens { + token + dataType + typeEnum + guid + tokenMetadata { + name + deprecatedTableGuid + deprecatedTableName + isFormula + rootTables { + created + description + guid + indexVersion + modified + name + __typename + } + schemaTableUserDefinedName + table { + created + description + guid + indexVersion + modified + name + __typename + } + __typename + } + __typename + } + ambiguityType + token { + token + dataType + typeEnum + guid + tokenMetadata { + name + __typename + } + __typename + } + __typename + } + stateKey { + transactionId + generationNumber + transactionId + __typename + } + subQueries { + tokens + cohortConfig { + anchorColumnId + cohortAnswerGuid + cohortGroupingType + cohortGuid + cohortType + combineNonGroupValues + description + groupExcludedQueryValues + hideExcludedQueryValues + isEditable + name + nullOutputValue + returnColumnId + __typename + } + formulas { + name + expression + __typename + } + __typename + } + visualizationSuggestion { + chartType + displayMode + axisConfigs { + category + color + hidden + size + sort + x + y + __typename + } + usersVizIntentApplied + customChartConfigs { + dimensions { + columns + key + __typename + } + key + __typename + } + customChartGuid + __typename + } + tableData { + columnDataLite { + columnId + columnDataType + dataValue + columnName + __typename + } + __typename + } + warningType + cached + __typename + } + debugInfo { + fewShotExamples { + chartType + id + mappingId + nlQuery + nlQueryConcepts + sageQuery + scope + sql + tml + __typename + } + __typename + } + __typename + } + __typename + } + errorData { + tool + errCode + errTxt + toolErrCode + __typename + } + __typename + } + type + __typename + } + prevCtx { + genNo + sessionId + __typename + } + __typename + } +} +`; + +class Conversation { + constructor(thoughtSpotHost, worksheetId) { + this.thoughtSpotHost = thoughtSpotHost; + this.worksheetId = worksheetId; + this.inProgress = null; + this.inProgress = this.init(); + } + async init() { + const { convId } = await this.createConversation(); + this.conversationId = convId; + } + createConversation() { + return this.executeQuery(createConversation, { + params: { + initialCtx: { + tsWorksheetCtx: { + worksheet: { + worksheetId: this.worksheetId, + }, + }, + type: 'TS_WORKSHEET', + }, + userInfo: { + tenantUrl: `${this.thoughtSpotHost}/prism`, + }, + }, + }); + } + async sendMessage(userMessage) { + await this.inProgress; + try { + const { responses } = await this.executeQuery(sendMessage, { + params: { + convId: this.conversationId, + headers: [], + msg: { + data: { + userCmdData: { + cmdText: userMessage, + nlsData: { + worksheetId: this.worksheetId, + questionType: 'ANSWER_SPEC_GENERATION', + }, + }, + }, + msgId: crypto.randomUUID(), + type: 'USER_COMMAND', + }, + }, + }); + const data = responses[0].data; + return { + convId: this.conversationId, + messageId: responses[0].msgId, + data: { + ...data.asstRespData.nlsAnsData.sageQuerySuggestions[0], + convId: this.conversationId, + messageId: responses[0].msgId, + }, + error: null, + }; + } + catch (error) { + return { error }; + } + } + async executeQuery(query, variables) { + return graphqlQuery({ + query, + variables, + thoughtSpotHost: this.thoughtSpotHost, + isCompositeQuery: false, + }); + } +} + +let ConversationMessage$1 = class ConversationMessage extends TsEmbed { + constructor(container, viewConfig) { + viewConfig.embedComponentType = 'bodyless-conversation'; + super(container, viewConfig); + this.viewConfig = viewConfig; + } + getEmbedParamsObject() { + const queryParams = this.getBaseQueryParams(); + queryParams[Param.HideActions] = [...(queryParams[Param.HideActions] ?? [])]; + queryParams[Param.isSpotterAgentEmbed] = true; + return queryParams; + } + getIframeSrc() { + const { sessionId, genNo, acSessionId, acGenNo, convId, messageId, } = this.viewConfig; + const path = 'conv-assist-answer'; + const queryParams = this.getEmbedParamsObject(); + let query = ''; + const queryParamsString = getQueryParamString(queryParams, true); + if (queryParamsString) { + query = `?${queryParamsString}`; + } + const tsPostHashParams = this.getThoughtSpotPostUrlParams({ + sessionId, + genNo, + acSessionId, + acGenNo, + convId, + messageId, + }); + return `${this.getEmbedBasePath(query)}/embed/${path}${tsPostHashParams}`; + } + async render() { + await super.render(); + const src = this.getIframeSrc(); + await this.renderIFrame(src); + return this; + } +}; +/** + * Create a conversation embed, which can be integrated inside + * chatbots or other conversational interfaces. + * @version SDK: 1.37.0 | ThoughtSpot: 10.9.0.cl + * @group Embed components + * @example + * ```js + * import { SpotterAgentEmbed } from '@thoughtspot/visual-embed-sdk'; + * + * const conversation = new SpotterAgentEmbed({ + * worksheetId: 'worksheetId', + * }); + * + * const { container, error } = await conversation.sendMessage('show me sales by region'); + * + * // append the container to the DOM + * document.body.appendChild(container); // or to any other element + * ``` + */ +class SpotterAgentEmbed { + constructor(viewConfig) { + this.viewConfig = viewConfig; + const embedConfig = getEmbedConfig(); + this.conversationService = new Conversation(embedConfig.thoughtSpotHost, viewConfig.worksheetId); + } + async sendMessage(userMessage) { + const { data, error } = await this.conversationService.sendMessage(userMessage); + if (error) { + return { error }; + } + const container = document.createElement('div'); + const embed = new ConversationMessage$1(container, { + ...this.viewConfig, + convId: data.convId, + messageId: data.messageId, + sessionId: data.sessionId, + genNo: data.genNo, + acSessionId: data.stateKey.transactionId, + acGenNo: data.stateKey.generationNumber, + }); + await embed.render(); + return { container, viz: embed }; + } + /** + * Send a message to the conversation service and return only the data. + * @param userMessage - The message to send to the conversation service. + * @returns The data from the conversation service. + */ + async sendMessageData(userMessage) { + try { + const { data, error } = await this.conversationService.sendMessage(userMessage); + if (error) { + return { error }; + } + return { data: { + convId: data.convId, + messageId: data.messageId, + sessionId: data.sessionId, + genNo: data.genNo, + acSessionId: data.stateKey.transactionId, + acGenNo: data.stateKey.generationNumber, + } }; + } + catch (error) { + return { error: error }; + } + } +} + +/** + * + * @param props + */ +function getViewPropsAndListeners(props) { + return Object.keys(props).reduce((accu, key) => { + if (key.startsWith('on')) { + const eventName = key.substr(2); + accu.listeners[EmbedEvent[eventName]] = props[key]; + } + else { + accu.viewConfig[key] = props[key]; + } + return accu; + }, { + viewConfig: {}, + listeners: {}, + }); +} + +/** + * Embed ThoughtSpot AI Conversation. + * @version SDK: 1.37.0 | ThoughtSpot: 10.9.0.cl + * @group Embed components + * @example + * ```js + * const conversation = new SpotterEmbed('#tsEmbed', { + * worksheetId: 'worksheetId', + * searchOptions: { + * searchQuery: 'searchQuery', + * }, + * }); + * conversation.render(); + * ``` + */ +let SpotterEmbed$1 = class SpotterEmbed extends TsEmbed { + constructor(container, viewConfig) { + viewConfig = { + embedComponentType: 'conversation', + excludeRuntimeFiltersfromURL: true, + excludeRuntimeParametersfromURL: true, + ...viewConfig, + }; + super(container, viewConfig); + this.viewConfig = viewConfig; + } + /** + * Extends the default APP_INIT payload with `embedParams.spotterSidebarConfig` + * so the conv-assist app can read sidebar configuration on initialisation. + * + * Precedence for `enablePastConversationsSidebar`: + * `spotterSidebarConfig.enablePastConversationsSidebar` wins over the + * deprecated top-level `enablePastConversationsSidebar` flag; if the former + * is absent the latter is used as a fallback. + * + * An invalid `spotterDocumentationUrl` triggers a validation error and is + * excluded from the payload rather than forwarded to the app. + */ + async getAppInitData() { + const defaultAppInitData = await super.getAppInitData(); + return buildSpotterSidebarAppInitData(defaultAppInitData, this.viewConfig, this.handleError.bind(this)); + } + getEmbedParamsObject() { + const { worksheetId, searchOptions, disableSourceSelection, hideSourceSelection, dataPanelV2, showSpotterLimitations, hideSampleQuestions, runtimeFilters, excludeRuntimeFiltersfromURL, runtimeParameters, excludeRuntimeParametersfromURL, updatedSpotterChatPrompt, enableStopAnswerGenerationEmbed, spotterChatConfig, } = this.viewConfig; + if (!worksheetId) { + this.handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.SPOTTER_EMBED_WORKSHEED_ID_NOT_FOUND, + code: EmbedErrorCodes.WORKSHEET_ID_NOT_FOUND, + error: ERROR_MESSAGE.SPOTTER_EMBED_WORKSHEED_ID_NOT_FOUND, + }); + } + const queryParams = this.getBaseQueryParams(); + queryParams[Param.SpotterEnabled] = true; + // Boolean params + setParamIfDefined(queryParams, Param.DisableSourceSelection, disableSourceSelection, true); + setParamIfDefined(queryParams, Param.HideSourceSelection, hideSourceSelection, true); + setParamIfDefined(queryParams, Param.DataPanelV2Enabled, dataPanelV2, true); + setParamIfDefined(queryParams, Param.ShowSpotterLimitations, showSpotterLimitations, true); + setParamIfDefined(queryParams, Param.HideSampleQuestions, hideSampleQuestions, true); + setParamIfDefined(queryParams, Param.UpdatedSpotterChatPrompt, updatedSpotterChatPrompt, true); + setParamIfDefined(queryParams, Param.EnableStopAnswerGenerationEmbed, enableStopAnswerGenerationEmbed, true); + // Handle spotterChatConfig params + if (spotterChatConfig) { + const { hideToolResponseCardBranding, toolResponseCardBrandingLabel, spotterFileUploadEnabled, spotterFileUploadFileTypes, } = spotterChatConfig; + setParamIfDefined(queryParams, Param.HideToolResponseCardBranding, hideToolResponseCardBranding, true); + setParamIfDefined(queryParams, Param.ToolResponseCardBrandingLabel, toolResponseCardBrandingLabel); + setParamIfDefined(queryParams, Param.SpotterFileUploadEnabled, spotterFileUploadEnabled, true); + if (spotterFileUploadFileTypes !== undefined) { + queryParams[Param.SpotterFileUploadFileTypes] = JSON.stringify(spotterFileUploadFileTypes); + } + } + return queryParams; + } + getIframeSrc() { + const { worksheetId, searchOptions, runtimeFilters, excludeRuntimeFiltersfromURL, runtimeParameters, excludeRuntimeParametersfromURL, } = this.viewConfig; + const path = 'insights/conv-assist'; + const queryParams = this.getEmbedParamsObject(); + let query = ''; + const queryParamsString = getQueryParamString(queryParams, true); + if (queryParamsString) { + query = `?${queryParamsString}`; + } + const filterQuery = getFilterQuery(runtimeFilters || []); + if (filterQuery && !excludeRuntimeFiltersfromURL) { + query += `&${filterQuery}`; + } + const parameterQuery = getRuntimeParameters(runtimeParameters || []); + if (parameterQuery && !excludeRuntimeParametersfromURL) { + query += `&${parameterQuery}`; + } + const tsPostHashParams = this.getThoughtSpotPostUrlParams({ + worksheet: worksheetId, + query: searchOptions?.searchQuery || '', + }); + return `${this.getEmbedBasePath(query)}/embed/${path}${tsPostHashParams}`; + } + async render() { + await super.render(); + const src = this.getIframeSrc(); + await this.renderIFrame(src); + return this; + } +}; +/** + * Embed ThoughtSpot AI Conversation. + * Use {@link SpotterEmbed} instead + * @version SDK: 1.37.0 | ThoughtSpot: 10.9.0.cl + * @deprecated from SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + * @group Embed components + * @example + * ```js + * const conversation = new SpotterEmbed('#tsEmbed', { + * worksheetId: 'worksheetId', + * searchOptions: { + * searchQuery: 'searchQuery', + * }, + * }); + * conversation.render(); + * ``` + */ +let ConversationEmbed$1 = class ConversationEmbed extends SpotterEmbed$1 { + constructor(container, viewConfig) { + viewConfig = { + embedComponentType: 'conversation', + excludeRuntimeFiltersfromURL: true, + excludeRuntimeParametersfromURL: true, + ...viewConfig, + }; + super(container, viewConfig); + this.viewConfig = viewConfig; + } +}; + +const componentFactory = (EmbedConstructor, +// isPreRenderedComponent: Specifies whether the component being returned is +// intended for preRendering. If set to true, the component will call the +// Embed.preRender() method instead of the usual render method, and it will +// not be destroyed when the component is unmounted. +isPreRenderedComponent = false) => { + const Component = React__default.forwardRef((props, forwardedRef) => { + const ref = React__default.useRef(null); + const { className, style, ...embedProps } = props; + const { viewConfig, listeners } = getViewPropsAndListeners(embedProps); + const handleDestroy = (tsEmbed) => { + // do not destroy if it is a preRender component + if (isPreRenderedComponent) + return; + // if component is connected to a preRendered component + if (props.preRenderId) { + tsEmbed.hidePreRender(); + return; + } + tsEmbed.destroy(); + }; + const handlePreRenderRendering = (tsEmbed) => { + tsEmbed.preRender(); + }; + const handleDefaultRendering = (tsEmbed) => { + // if component is connected to a preRendered component + if (props.preRenderId) { + tsEmbed.showPreRender(); + return; + } + tsEmbed.render(); + }; + const handleRendering = (tsEmbed) => { + if (isPreRenderedComponent) { + handlePreRenderRendering(tsEmbed); + return; + } + handleDefaultRendering(tsEmbed); + }; + useDeepCompareEffect(() => { + const tsEmbed = new EmbedConstructor(ref.current, deepMerge({ + insertAsSibling: viewConfig.insertAsSibling, + frameParams: { + class: viewConfig.insertAsSibling ? className || '' : '', + }, + }, viewConfig)); + Object.keys(listeners).forEach((eventName) => { + tsEmbed.on(eventName, listeners[eventName]); + }); + handleRendering(tsEmbed); + if (forwardedRef) { + forwardedRef.current = tsEmbed; + } + return () => { + handleDestroy(tsEmbed); + }; + }, [viewConfig, listeners]); + const preRenderStyles = isPreRenderedComponent ? { display: 'none' } : {}; + // We dont add any component for preRenderedComponent + if (isPreRenderedComponent) + return React__default.createElement(React__default.Fragment, null); + return viewConfig.insertAsSibling ? (React__default.createElement("span", { "data-testid": "tsEmbed", ref: ref, style: { position: 'absolute', ...preRenderStyles } })) : (React__default.createElement("div", { "data-testid": "tsEmbed", ref: ref, style: { ...style, ...preRenderStyles }, className: `ts-embed-container ${className}` })); + }); + Component.displayName = EmbedConstructor.name || 'EmbedComponent'; + return Component; +}; +/** + * React component for Search Embed. + * @example + * ```tsx + * function Search() { + * return + * } + * ``` + */ +const SearchEmbed = componentFactory(SearchEmbed$1); +const PreRenderedSearchEmbed = componentFactory(SearchEmbed$1, true); +/** + * React component for Full app Embed. + * @example + * ```tsx + * function App() { + * return console.error(error)} + * /> + * } + * ``` + */ +const AppEmbed = componentFactory(AppEmbed$1); +/** + * React component for PreRendered App embed. + * + * PreRenderedAppEmbed will preRender the AppEmbed and will be hidden by + * default. + * + * AppEmbed with preRenderId passed will call showPreRender on the embed. + * @example + * ```tsx + * function LandingPageComponent() { + * return + * } + * ``` + * function MyComponent() { + * return + * } + * ``` + */ +const PreRenderedAppEmbed = componentFactory(AppEmbed$1, true); +/** + * React component for Liveboard embed. + * @example + * ```tsx + * function Liveboard() { + * return console.log('Liveboard rendered')} + * vizId="vizId" {/* if doing viz embed *\/} + * /> + * } + * ``` + */ +const LiveboardEmbed = componentFactory(LiveboardEmbed$1); +const PinboardEmbed = LiveboardEmbed; +/** + * React component for PreRendered Liveboard embed. + * + * PreRenderedLiveboardEmbed will preRender the liveboard and will be hidden by default. + * + * LiveboardEmbed with preRenderId passed will call showPreRender on the embed. + * + * If LiveboardEmbed is rendered before PreRenderedLiveboardEmbed is rendered it + * tries to preRender the LiveboardEmbed, so it is recommended to pass the + * liveboardId to both the components. + * @example + * ```tsx + * function LandingPageComponent() { + * return + * } + * ``` + * function MyComponent() { + * return + * } + * ``` + */ +const PreRenderedLiveboardEmbed = componentFactory(LiveboardEmbed$1, true); +const PreRenderedPinboardEmbed = PreRenderedLiveboardEmbed; +/** + * React component for Search bar embed. + * @example + * ```tsx + * function SearchBar() { + * return + * } + * ``` + */ +const SearchBarEmbed = componentFactory(SearchBarEmbed$1); +/** + * React component for PreRendered SearchBar embed. + * + * PreRenderedSearchBarEmbed will preRender the SearchBarEmbed and will be hidden by + * default. + * + * SearchBarEmbed with preRenderId passed will call showPreRender on the embed. + * @example + * ```tsx + * function LandingPageComponent() { + * return + * } + * ``` + * function MyComponent() { + * return + * } + * ``` + */ +const PreRenderedSearchBarEmbed = componentFactory(SearchBarEmbed$1, true); +/** + * React component for LLM based conversation BI. + * @example + * ```tsx + * function Sage() { + * return " + * }} + * ... other view config props or event listeners. + * /> + * } + * ``` + */ +const SpotterEmbed = componentFactory(SpotterEmbed$1); +/** + * React component for LLM based conversation BI. + * Use {@link SpotterEmbed} instead + * @deprecated from SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + * @example + * ```tsx + * function Sage() { + * return " + * }} + * ... other view config props or event listeners. + * /> + * } + * ``` + */ +const ConversationEmbed = componentFactory(ConversationEmbed$1); +const ConversationMessage = componentFactory(ConversationMessage$1); +/** + * React component for displaying individual conversation messages from SpotterAgent. + * + * This component renders a single message response from your ThoughtSpot conversation, + * showing charts, visualizations, or text responses based on the user's query. + * + * @version SDK: 1.39.0 | ThoughtSpot: 10.11.0.cl + * @example + * ```tsx + * const { sendMessage } = useSpotterAgent({ worksheetId: 'worksheetId' }); + * const result = await sendMessage('show me sales by region'); + * + * if (!result.error) { + * // Simple usage - just pass the message data + * + * + * // With optional query for context + * + * } + * ``` + */ +const SpotterMessage = React__default.forwardRef((props, ref) => { + const { message, query: _, ...otherProps } = props; + return (React__default.createElement(ConversationMessage, { ref: ref, ...message, ...otherProps })); +}); +SpotterMessage.displayName = 'SpotterMessage'; +/** + * React component for PreRendered Conversation embed. + * + * PreRenderedConversationEmbed will preRender the SpotterEmbed and will be hidden by + * default. + * + * SpotterEmbed with preRenderId passed will call showPreRender on the embed. + * @example + * ```tsx + * function LandingPageComponent() { + * return + * } + * ``` + * function MyComponent() { + * return + * } + * ``` + */ +const PreRenderedConversationEmbed = componentFactory(SpotterEmbed$1, true); +/** + * Get a reference to the embed component to trigger events on the component. + * @example + * ``` + * function Component() { + * const ref = useEmbedRef(); + * useEffect(() => { + * ref.current.trigger( + * EmbedEvent.UpdateRuntimeFilter, + * [{ columnName: 'name', operator: 'EQ', values: ['value']}]); + * }, []) + * return } /> + * } + * ``` + * @returns {React.MutableRefObject} ref + */ +function useEmbedRef() { + return React__default.useRef(null); +} +/** + * + * @version SDK: 1.36.2 | ThoughtSpot: * + * @param config - EmbedConfig + * @returns AuthEventEmitter + * @example + * ``` + * function Component() { + * const authEE = useInit({ ...initConfig }); + * return } /> + * } + * ``` + */ +function useInit(config) { + const ref = useRef(null); + useDeepCompareEffect(() => { + const authEE = init(config); + ref.current = authEE; + }, [config]); + return ref; +} +/** + * React hook for interacting with SpotterAgent AI conversations. + * + * This hook provides a sendMessage function that allows you to send natural language + * queries to your data and get back AI-generated responses with visualizations. + * + * @version SDK: 1.39.0 | ThoughtSpot: 10.11.0.cl + * @param config - Configuration object containing worksheetId and other options + * @returns Object with sendMessage function that returns conversation results + * @example + * ```tsx + * const { sendMessage } = useSpotterAgent({ worksheetId: 'worksheetId' }); + * + * const handleQuery = async () => { + * const result = await sendMessage('show me sales by region'); + * + * if (!result.error) { + * // Display the message response + * + * } else { + * console.error('Error:', result.error); + * } + * }; + * ``` + */ +function useSpotterAgent(config) { + const serviceRef = useRef(null); + useDeepCompareEffect(() => { + if (serviceRef.current) { + serviceRef.current = null; + } + serviceRef.current = new SpotterAgentEmbed(config); + return () => { + serviceRef.current = null; + }; + }, [config]); + const sendMessage = useCallback(async (query) => { + if (!serviceRef.current) { + return { error: new Error(ERROR_MESSAGE.SPOTTER_AGENT_NOT_INITIALIZED) }; + } + const result = await serviceRef.current.sendMessageData(query); + if (result.error) { + return { error: result.error }; + } + return { + query: query, + message: { + ...result.data, + worksheetId: config.worksheetId, + }, + }; + }, [config.worksheetId]); + return { + sendMessage, + }; +} + +export { Action, AppEmbed, ConversationEmbed, ConversationMessage, CustomActionsPosition, EmbedEvent, HomeLeftNavItem, HomepageModule, HostEvent, ListPageColumns, LiveboardEmbed, LogLevel, Page, PinboardEmbed, PreRenderedAppEmbed, PreRenderedConversationEmbed, PreRenderedLiveboardEmbed, PreRenderedPinboardEmbed, PreRenderedSearchBarEmbed, PreRenderedSearchEmbed, RuntimeFilterOp, SearchBarEmbed, SearchEmbed, SpotterEmbed, SpotterMessage, getSessionInfo, useEmbedRef, useInit, useSpotterAgent }; diff --git a/dist/tsembed-react.js b/dist/tsembed-react.js new file mode 100644 index 00000000..0f3bea6c --- /dev/null +++ b/dist/tsembed-react.js @@ -0,0 +1,31559 @@ +/* @thoughtspot/visual-embed-sdk version 1.48.0 */ +'use client'; +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react')) : + typeof define === 'function' && define.amd ? define(['exports', 'react'], factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.tsembed = {}, global.React)); +})(this, (function (exports, React) { 'use strict'; + + function _interopNamespaceDefault(e) { + var n = Object.create(null); + if (e) { + Object.keys(e).forEach(function (k) { + if (k !== 'default') { + var d = Object.getOwnPropertyDescriptor(e, k); + Object.defineProperty(n, k, d.get ? d : { + enumerable: true, + get: function () { return e[k]; } + }); + } + }); + } + n.default = e; + return Object.freeze(n); + } + + function _mergeNamespaces(n, m) { + m.forEach(function (e) { + e && typeof e !== 'string' && !Array.isArray(e) && Object.keys(e).forEach(function (k) { + if (k !== 'default' && !(k in n)) { + var d = Object.getOwnPropertyDescriptor(e, k); + Object.defineProperty(n, k, d.get ? d : { + enumerable: true, + get: function () { return e[k]; } + }); + } + }); + }); + return Object.freeze(n); + } + + var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React); + + var has = Object.prototype.hasOwnProperty; + + function find(iter, tar, key) { + for (key of iter.keys()) { + if (dequal(key, tar)) return key; + } + } + + function dequal(foo, bar) { + var ctor, len, tmp; + if (foo === bar) return true; + + if (foo && bar && (ctor=foo.constructor) === bar.constructor) { + if (ctor === Date) return foo.getTime() === bar.getTime(); + if (ctor === RegExp) return foo.toString() === bar.toString(); + + if (ctor === Array) { + if ((len=foo.length) === bar.length) { + while (len-- && dequal(foo[len], bar[len])); + } + return len === -1; + } + + if (ctor === Set) { + if (foo.size !== bar.size) { + return false; + } + for (len of foo) { + tmp = len; + if (tmp && typeof tmp === 'object') { + tmp = find(bar, tmp); + if (!tmp) return false; + } + if (!bar.has(tmp)) return false; + } + return true; + } + + if (ctor === Map) { + if (foo.size !== bar.size) { + return false; + } + for (len of foo) { + tmp = len[0]; + if (tmp && typeof tmp === 'object') { + tmp = find(bar, tmp); + if (!tmp) return false; + } + if (!dequal(len[1], bar.get(tmp))) { + return false; + } + } + return true; + } + + if (ctor === ArrayBuffer) { + foo = new Uint8Array(foo); + bar = new Uint8Array(bar); + } else if (ctor === DataView) { + if ((len=foo.byteLength) === bar.byteLength) { + while (len-- && foo.getInt8(len) === bar.getInt8(len)); + } + return len === -1; + } + + if (ArrayBuffer.isView(foo)) { + if ((len=foo.byteLength) === bar.byteLength) { + while (len-- && foo[len] === bar[len]); + } + return len === -1; + } + + if (!ctor || typeof foo === 'object') { + len = 0; + for (ctor in foo) { + if (has.call(foo, ctor) && ++len && !has.call(bar, ctor)) return false; + if (!(ctor in bar) || !dequal(foo[ctor], bar[ctor])) return false; + } + return Object.keys(bar).length === len; + } + } + + return foo !== foo && bar !== bar; + } + + /** + * @param value the value to be memoized (usually a dependency list) + * @returns a momoized version of the value as long as it remains deeply equal + */ + + + function useDeepCompareMemoize(value) { + var ref = React__namespace.useRef(value); + var signalRef = React__namespace.useRef(0); + + if (!dequal(value, ref.current)) { + ref.current = value; + signalRef.current += 1; + } // eslint-disable-next-line react-hooks/exhaustive-deps + + + return React__namespace.useMemo(function () { + return ref.current; + }, [signalRef.current]); + } + + function useDeepCompareEffect(callback, dependencies) { + + + return React__namespace.useEffect(callback, useDeepCompareMemoize(dependencies)); + } + + // istanbul ignore next + const isObject$1 = (obj) => { + if (typeof obj === "object" && obj !== null) { + if (typeof Object.getPrototypeOf === "function") { + const prototype = Object.getPrototypeOf(obj); + return prototype === Object.prototype || prototype === null; + } + return Object.prototype.toString.call(obj) === "[object Object]"; + } + return false; + }; + const merge = (...objects) => objects.reduce((result, current) => { + if (Array.isArray(current)) { + throw new TypeError("Arguments provided to ts-deepmerge must be objects, not arrays."); + } + Object.keys(current).forEach((key) => { + if (["__proto__", "constructor", "prototype"].includes(key)) { + return; + } + if (Array.isArray(result[key]) && Array.isArray(current[key])) { + result[key] = merge.options.mergeArrays + ? merge.options.uniqueArrayItems + ? Array.from(new Set(result[key].concat(current[key]))) + : [...result[key], ...current[key]] + : current[key]; + } + else if (isObject$1(result[key]) && isObject$1(current[key])) { + result[key] = merge(result[key], current[key]); + } + else { + result[key] = + current[key] === undefined + ? merge.options.allowUndefinedOverrides + ? current[key] + : result[key] + : current[key]; + } + }); + return result; + }, {}); + const defaultOptions = { + allowUndefinedOverrides: true, + mergeArrays: true, + uniqueArrayItems: true, + }; + merge.options = defaultOptions; + merge.withOptions = (options, ...objects) => { + merge.options = Object.assign(Object.assign({}, defaultOptions), options); + const result = merge(...objects); + merge.options = defaultOptions; + return result; + }; + + /** + * Copyright (c) 2023 + * + * TypeScript type definitions for ThoughtSpot Visual Embed SDK + * @summary Type definitions for Embed SDK + * @author Ayon Ghosh + */ + /** + * The authentication mechanism for allowing access to + * the embedded app + * @group Authentication / Init + */ + var AuthType; + (function (AuthType) { + /** + * No authentication on the SDK. Pass-through to the embedded App. Alias for + * `Passthrough`. + * @example + * ```js + * init({ + * // ... + * authType: AuthType.None, + * }); + * ``` + */ + AuthType["None"] = "None"; + /** + * Passthrough SSO to the embedded application within the iframe. Requires least + * configuration, but may not be supported by all IDPs. This will behave like `None` + * if SSO is not configured on ThoughtSpot. + * + * To use this: + * Your SAML or OpenID provider must allow iframe redirects. + * For example, if you are using Okta as IdP, you can enable iframe embedding. + * @version SDK: 1.15.0 | ThoughtSpot: 8.8.0.cl + * @example + * ```js + * init({ + * // ... + * authType: AuthType.EmbeddedSSO, + * }); + * ``` + */ + AuthType["EmbeddedSSO"] = "EmbeddedSSO"; + /** + * SSO using SAML, Use {@link SAMLRedirect} instead + * @deprecated This option is deprecated. + * @hidden + */ + AuthType["SSO"] = "SSO_SAML"; + /** + * SSO using SAML, Use {@link SAMLRedirect} instead + * @deprecated This option is deprecated. + * @hidden + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + AuthType["SAML"] = "SSO_SAML"; + /** + * SSO using SAML + * Makes the host application redirect to the SAML IdP. Use this + * if your IdP does not allow itself to be embedded. + * + * This redirects the host application to the SAML IdP. The host application + * will be redirected back to the ThoughtSpot app after authentication. + * @example + * ```js + * init({ + * // ... + * authType: AuthType.SAMLRedirect, + * }); + * ``` + * + * This opens the SAML IdP in a popup window. The popup is triggered + * when the user clicks the trigger button. The popup window will be + * closed automatically after authentication. + * @example + * ```js + * init({ + * // ... + * authType: AuthType.SAMLRedirect, + * authTriggerText: 'Login with SAML', + * authTriggerContainer: '#tsEmbed', + * inPopup: true, + * }); + * ``` + * + * Can also use the event to trigger the popup flow. Works the same + * as the above example. + * @example + * ```js + * const authEE = init({ + * // ... + * authType: AuthType.SAMLRedirect, + * inPopup: true, + * }); + * + * someButtonOnYourPage.addEventListener('click', () => { + * authEE.emit(AuthEvent.TRIGGER_SSO_POPUP); + * }); + * ``` + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + AuthType["SAMLRedirect"] = "SSO_SAML"; + /** + * SSO using OIDC + * SSO using OIDC, Use {@link OIDCRedirect} instead + * @deprecated This option is deprecated. + * @hidden + */ + AuthType["OIDC"] = "SSO_OIDC"; + /** + * SSO using OIDC + * Will make the host application redirect to the OIDC IdP. + * See code samples in {@link SAMLRedirect}. + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + AuthType["OIDCRedirect"] = "SSO_OIDC"; + /** + * Trusted authentication server + * Use {@link TrustedAuth} instead + * @deprecated This option is deprecated. + * @hidden + */ + AuthType["AuthServer"] = "AuthServer"; + /** + * Trusted authentication server. Use your own authentication server + * which returns a bearer token, generated using the `secret_key` obtained + * from ThoughtSpot. + * @example + * ```js + * init({ + * // ... + * authType: AuthType.TrustedAuthToken, + * getAuthToken: () => { + * return fetch('https://my-backend.app/ts-token') + * .then((response) => response.json()) + * .then((data) => data.token); + * } + * }); + * ``` + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + AuthType["TrustedAuthToken"] = "AuthServer"; + /** + * Trusted authentication server Cookieless, Use your own authentication + * server which returns a bearer token, generated using the `secret_key` + * obtained from ThoughtSpot. This uses a cookieless authentication + * approach, recommended to bypass the third-party cookie-blocking restriction + * implemented by some browsers. + * @version SDK: 1.22.0 | ThoughtSpot: 9.3.0.cl, 9.5.1.sw + * @example + * ```js + * init({ + * // ... + * authType: AuthType.TrustedAuthTokenCookieless, + * getAuthToken: () => { + * return fetch('https://my-backend.app/ts-token') + * .then((response) => response.json()) + * .then((data) => data.token); + * } + * }); + * ``` + */ + AuthType["TrustedAuthTokenCookieless"] = "AuthServerCookieless"; + /** + * Use the ThoughtSpot login API to authenticate to the cluster directly. + * + * Warning: This feature is primarily intended for developer testing. It is + * strongly advised not to use this authentication method in production. + */ + AuthType["Basic"] = "Basic"; + })(AuthType || (AuthType = {})); + /** + * + * **Note**: This attribute is not supported in the classic (V1) homepage experience. + * + */ + exports.HomeLeftNavItem = void 0; + (function (HomeLeftNavItem) { + /** + * The *Search data* option in + * the *Insights* left navigation panel. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + */ + HomeLeftNavItem["SearchData"] = "search-data"; + /** + * The *Home* menu option in + * the *Insights* left navigation panel. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + */ + HomeLeftNavItem["Home"] = "insights-home"; + /** + * The *Liveboards* menu option in + * the *Insights* left navigation panel. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + */ + HomeLeftNavItem["Liveboards"] = "liveboards"; + /** + * The *Answers* menu option in + * the *Insights* left navigation panel. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + */ + HomeLeftNavItem["Answers"] = "answers"; + /** + * The *Monitor subscriptions* menu option in + * the *Insights* left navigation panel. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + */ + HomeLeftNavItem["MonitorSubscription"] = "monitor-alerts"; + /** + * The *SpotIQ analysis* menu option in + * the *Insights* left navigation panel. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + */ + HomeLeftNavItem["SpotIQAnalysis"] = "spotiq-analysis"; + /** + * The *Liveboard schedules* menu option in + * the *Insights* left navigation panel. + * @version SDK: 1.34.0 | ThoughtSpot: 10.3.0.cl + */ + HomeLeftNavItem["LiveboardSchedules"] = "liveboard-schedules"; + /** + * The create option in the *Insights* + * left navigation panel. + * Available in the V3 navigation experience. + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HomeLeftNavItem["Create"] = "create"; + /** + * The *Spotter* menu option in the *Insights* + * left navigation panel. + * Available in the V3 navigation experience. + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HomeLeftNavItem["Spotter"] = "spotter"; + /** + * The *Favorites* section in the *Insights* + * left navigation panel. + * Available in the V3 navigation experience. + * @version SDK: 1.41.0 | ThoughtSpot: 10.12.0.cl + */ + HomeLeftNavItem["Favorites"] = "favorites"; + })(exports.HomeLeftNavItem || (exports.HomeLeftNavItem = {})); + /** + * A map of the supported runtime filter operations + */ + exports.RuntimeFilterOp = void 0; + (function (RuntimeFilterOp) { + /** + * Equals + */ + RuntimeFilterOp["EQ"] = "EQ"; + /** + * Does not equal + */ + RuntimeFilterOp["NE"] = "NE"; + /** + * Less than + */ + RuntimeFilterOp["LT"] = "LT"; + /** + * Less than or equal to + */ + RuntimeFilterOp["LE"] = "LE"; + /** + * Greater than + */ + RuntimeFilterOp["GT"] = "GT"; + /** + * Greater than or equal to + */ + RuntimeFilterOp["GE"] = "GE"; + /** + * Contains + */ + RuntimeFilterOp["CONTAINS"] = "CONTAINS"; + /** + * Begins with + */ + RuntimeFilterOp["BEGINS_WITH"] = "BEGINS_WITH"; + /** + * Ends with + */ + RuntimeFilterOp["ENDS_WITH"] = "ENDS_WITH"; + /** + * Between, inclusive of higher value + */ + RuntimeFilterOp["BW_INC_MAX"] = "BW_INC_MAX"; + /** + * Between, inclusive of lower value + */ + RuntimeFilterOp["BW_INC_MIN"] = "BW_INC_MIN"; + /** + * Between, inclusive of both higher and lower value + */ + RuntimeFilterOp["BW_INC"] = "BW_INC"; + /** + * Between, non-inclusive + */ + RuntimeFilterOp["BW"] = "BW"; + /** + * Is included in this list of values + */ + RuntimeFilterOp["IN"] = "IN"; + /** + * Is not included in this list of values + */ + RuntimeFilterOp["NOT_IN"] = "NOT_IN"; + })(exports.RuntimeFilterOp || (exports.RuntimeFilterOp = {})); + /** + * Home page modules that can be hidden + * via `hiddenHomepageModules` and reordered via + * `reorderedHomepageModules`. + * + * **Note**: This option is not supported in the classic (v1) experience. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl, 10.1.0.sw + */ + exports.HomepageModule = void 0; + (function (HomepageModule) { + /** + * Search bar + */ + HomepageModule["Search"] = "SEARCH"; + /** + * KPI watchlist module + */ + HomepageModule["Watchlist"] = "WATCHLIST"; + /** + * Favorite module + */ + HomepageModule["Favorite"] = "FAVORITE"; + /** + * List of answers and Liveboards + */ + HomepageModule["MyLibrary"] = "MY_LIBRARY"; + /** + * Trending list + */ + HomepageModule["Trending"] = "TRENDING"; + /** + * Learning videos + */ + HomepageModule["Learning"] = "LEARNING"; + })(exports.HomepageModule || (exports.HomepageModule = {})); + /** + * List page columns that can be hidden. + * **Note**: This option is applicable to full app embedding only. + * @version SDK: 1.38.0 | ThoughtSpot: 10.9.0.cl + */ + exports.ListPageColumns = void 0; + (function (ListPageColumns) { + /** + * Favorites + */ + ListPageColumns["Favorites"] = "FAVOURITE"; + /** + * Favourite Use {@link ListPageColumns.Favorites} instead. + * @deprecated This option is deprecated. + */ + ListPageColumns["Favourite"] = "FAVOURITE"; + /** + * Tags + */ + ListPageColumns["Tags"] = "TAGS"; + /** + * Author + */ + ListPageColumns["Author"] = "AUTHOR"; + /** + * Last viewed/Last modified + */ + ListPageColumns["DateSort"] = "DATE_SORT"; + /** + * Share + */ + ListPageColumns["Share"] = "SHARE"; + /** + * Verified badge/column + */ + ListPageColumns["Verified"] = "VERIFIED"; + })(exports.ListPageColumns || (exports.ListPageColumns = {})); + /** + * Event types emitted by the embedded ThoughtSpot application. + * + * To add an event listener use the corresponding + * {@link LiveboardEmbed.on} or {@link AppEmbed.on} or {@link SearchEmbed.on} method. + * @example + * ```js + * import { EmbedEvent } from '@thoughtspot/visual-embed-sdk'; + * // Or + * // const { EmbedEvent } = window.tsembed; + * + * // create the liveboard embed. + * + * liveboardEmbed.on(EmbedEvent.Drilldown, (drilldown) => { + * console.log('Drilldown event', drilldown); + * })); + * ``` + * + * If you are using React components for embedding, you can register to any + * events from the `EmbedEvent` list by using the `on` convention. + * For example,`onAlert`, `onCopyToClipboard` and so on. + * @example + * ```js + * // ... + * const MyComponent = ({ dataSources }) => { + * const onLoad = () => { + * console.log(EmbedEvent.Load, {}); + * }; + * + * return ( + * + * ); + * }; + * ``` + * @group Events + */ + exports.EmbedEvent = void 0; + (function (EmbedEvent) { + /** + * Rendering has initialized. + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Init, showLoader) + * //show a loader + * function showLoader() { + * document.getElementById("loader"); + * } + * ``` + * @returns timestamp - The timestamp when the event was generated. + */ + EmbedEvent["Init"] = "init"; + /** + * Authentication has either succeeded or failed. + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.AuthInit, payload => { + * console.log('AuthInit', payload); + * }) + * ``` + * @returns isLoggedIn - A Boolean specifying whether authentication was successful. + */ + EmbedEvent["AuthInit"] = "authInit"; + /** + * The embed object container has loaded. + * @returns timestamp - The timestamp when the event was generated. + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Load, hideLoader) + * //hide loader + * function hideLoader() { + * document.getElementById("loader"); + * } + * ``` + */ + EmbedEvent["Load"] = "load"; + /** + * Data pertaining to an Answer, Liveboard or Spotter visualization is received. + * The event payload includes the raw data of the object. + * @return data - Answer of Liveboard data + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Data, payload => { + * console.log('data', payload); + * }) + * ``` + * @important + */ + EmbedEvent["Data"] = "data"; + /** + * Search query has been updated by the user. + * @version SDK: 1.4.0 | ThoughtSpot: ts7.sep.cl, 8.4.1.sw + * @example + * ```js + * searchEmbed.on(EmbedEvent.QueryChanged, payload => console.log('data', payload)) + * ``` + */ + EmbedEvent["QueryChanged"] = "queryChanged"; + /** + * A drill-down operation has been performed. + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @returns additionalFilters - Any additional filters applied + * @returns drillDownColumns - The columns on which drill down was performed + * @returns nonFilteredColumns - The columns that were not filtered + * @example + * ```js + * searchEmbed.on(EmbedEvent.DrillDown, { + * points: { + * clickedPoint, + * selectedPoints: selectedPoint + * }, + * autoDrillDown: true, + * }) + * ``` + * In this example, `VizPointDoubleClick` event is used for + * triggering the `DrillDown` event when an area or specific + * data point on a table or chart is double-clicked. + * @example + * ```js + * searchEmbed.on(EmbedEvent.VizPointDoubleClick, (payload) => { + * console.log(payload); + * const clickedPoint = payload.data.clickedPoint; + * const selectedPoint = payload.data.selectedPoints; + * console.log('>>> called', clickedPoint); + * embed.trigger(HostEvent.DrillDown, { + * points: { + * clickedPoint, + * selectedPoints: selectedPoint + * }, + * autoDrillDown: true, + * }) + * }) + * ``` + */ + EmbedEvent["Drilldown"] = "drillDown"; + /** + * One or more data sources have been selected. + * @returns dataSourceIds - the list of data sources + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * searchEmbed.on(EmbedEvent.DataSourceSelected, payload => { + * console.log('DataSourceSelected', payload); + * }) + * ``` + */ + EmbedEvent["DataSourceSelected"] = "dataSourceSelected"; + /** + * One or more data columns have been selected. + * @returns columnIds - the list of columns + * @version SDK: 1.10.0 | ThoughtSpot: 8.2.0.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.AddRemoveColumns, payload => { + * console.log('AddRemoveColumns', payload); + * }) + * ``` + */ + EmbedEvent["AddRemoveColumns"] = "addRemoveColumns"; + /** + * A custom action has been triggered. + * @returns actionId - ID of the custom action + * @returns payload {@link CustomActionPayload} - Response payload with the + * Answer or Liveboard data + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.customAction, payload => { + * const data = payload.data; + * if (data.id === 'insert Custom Action ID here') { + * console.log('Custom Action event:', data.embedAnswerData); + * } + * }) + * ``` + */ + EmbedEvent["CustomAction"] = "customAction"; + /** + * Listen to double click actions on a visualization. + * @return ContextMenuInputPoints - Data point that is double-clicked + * @version SDK: 1.5.0 | ThoughtSpot: ts7.oct.cl, 7.2.1 + * @example + * ```js + * LiveboardEmbed.on(EmbedEvent.VizPointDoubleClick, payload => { + * console.log('VizPointDoubleClick', payload); + * }) + * ``` + */ + EmbedEvent["VizPointDoubleClick"] = "vizPointDoubleClick"; + /** + * Listen to clicks on a visualization in a Liveboard or Search result. + * @return viz, clickedPoint - metadata about the point that is clicked + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @important + * @example + * ```js + * embed.on(EmbedEvent.VizPointClick, ({data}) => { + * console.log( + * data.vizId, // viz id + * data.clickedPoint.selectedAttributes[0].value, + * data.clickedPoint.selectedAttributes[0].column.name, + * data.clickedPoint.selectedMeasures[0].value, + * data.clickedPoint.selectedMeasures[0].column.name, + * ) + * }); + * ``` + */ + EmbedEvent["VizPointClick"] = "vizPointClick"; + /** + * Fired when an error occurs in the embedded component. + * + * **Important:** This event fires for many reasons — including internal + * validation warnings (e.g. `HOST_EVENT_VALIDATION`), configuration issues, + * and transient errors that ThoughtSpot already handles gracefully inside the + * iframe. **Do not call `embed.destroy()` or unmount the embed component on + * every error.** Doing so will tear down the iframe and abort all in-flight + * requests, causing the embed to fail entirely. + * + * Only treat the following codes as unrecoverable: + * - `INIT_ERROR` — SDK was not initialised before render + * - `LOGIN_FAILED` — authentication could not be completed + * + * All other error codes should be logged and inspected, not acted upon + * destructively. + * + * **Note:** There is currently no dedicated event for a true unrecoverable + * crash. A future `EmbedEvent.FatalError` event is planned to give customers + * a clean signal for when the embed cannot recover and needs to be torn down. + * + * Error types include: + * `API` - API call failure. + * `FULLSCREEN` - Error when presenting a Liveboard in full screen mode. + * `VALIDATION_ERROR` - Internal host event or configuration validation warning. + * + * For more information, see https://developers.thoughtspot.com/docs/events-app-integration#errorType + * @returns error - An error object or message + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * // Recommended pattern — only destroy on truly fatal errors + * embed.on(EmbedEvent.Error, (error) => { + * const FATAL_CODES = ['INIT_ERROR', 'LOGIN_FAILED']; + * if (FATAL_CODES.includes(error.data?.code)) { + * embed.destroy(); + * return; + * } + * // Log all other errors — do not destroy + * console.warn('Embed error (non-fatal):', error); + * }); + * ``` + * @example + * ```js + * // API error + * SearchEmbed.on(EmbedEvent.Error, (error) => { + * console.log(error); + * // { errorType: "API", message: '...', code: '...' } + * }); + * ``` + */ + EmbedEvent["Error"] = "Error"; + /** + * The embedded object has sent an alert. + * @returns alert - An alert object + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * searchEmbed.on(EmbedEvent.Alert) + * ``` + */ + EmbedEvent["Alert"] = "alert"; + /** + * The ThoughtSpot authentication session has expired. + * @version SDK: 1.4.0 | ThoughtSpot: ts7.sep.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.AuthExpire, showAuthExpired) + * //show auth expired banner + * function showAuthExpired() { + * document.getElementById("authExpiredBanner"); + * } + * ``` + */ + EmbedEvent["AuthExpire"] = "ThoughtspotAuthExpired"; + /** + * ThoughtSpot failed to validate the auth session. + * @hidden + */ + EmbedEvent["AuthFailure"] = "ThoughtspotAuthFailure"; + /** + * ThoughtSpot failed to re validate the auth session. + * @hidden + */ + EmbedEvent["IdleSessionTimeout"] = "IdleSessionTimeout"; + /** + * ThoughtSpot failed to validate the auth session. + * @hidden + */ + EmbedEvent["AuthLogout"] = "ThoughtspotAuthLogout"; + /** + * The height of the embedded Liveboard or visualization has been computed. + * @returns data - The height of the embedded Liveboard or visualization + * @hidden + */ + EmbedEvent["EmbedHeight"] = "EMBED_HEIGHT"; + /** + * The center of visible iframe viewport is calculated. + * @returns data - The center of the visible Iframe viewport. + * @hidden + */ + EmbedEvent["EmbedIframeCenter"] = "EmbedIframeCenter"; + /** + * Emitted when the **Get Data** action is initiated. + * Applicable to `SearchBarEmbed` only. + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + * @example + * ```js + * searchbarEmbed.on(EmbedEvent.GetDataClick) + * .then(data => { + * console.log('Answer Data:', data); + * }) + * ``` + */ + EmbedEvent["GetDataClick"] = "getDataClick"; + /** + * Detects the route change. + * @version SDK: 1.7.0 | ThoughtSpot: 8.0.0.cl, 8.4.1.sw + * @example + * ```js + * searchEmbed.on(EmbedEvent.RouteChange, payload => + * console.log('data', payload)) + * ``` + */ + EmbedEvent["RouteChange"] = "ROUTE_CHANGE"; + /** + * The v1 event type for Data + * @hidden + */ + EmbedEvent["V1Data"] = "exportVizDataToParent"; + /** + * Emitted when the embed does not have cookie access. This happens + * when third-party cookies are blocked by Safari or other + * web browsers. `NoCookieAccess` can trigger. + * @example + * ```js + * appEmbed.on(EmbedEvent.NoCookieAccess) + * ``` + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 7.2.1.sw + */ + EmbedEvent["NoCookieAccess"] = "noCookieAccess"; + /** + * Emitted when SAML is complete + * @private + * @hidden + */ + EmbedEvent["SAMLComplete"] = "samlComplete"; + /** + * Emitted when any modal is opened in the app + * @version SDK: 1.6.0 | ThoughtSpot: ts8.nov.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.DialogOpen, payload => { + * console.log('dialog open', payload); + * }) + * ``` + */ + EmbedEvent["DialogOpen"] = "dialog-open"; + /** + * Emitted when any modal is closed in the app + * @version SDK: 1.6.0 | ThoughtSpot: ts8.nov.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.DialogClose, payload => { + * console.log('dialog close', payload); + * }) + * ``` + */ + EmbedEvent["DialogClose"] = "dialog-close"; + /** + * Emitted when the Liveboard shell loads. + * You can use this event as a hook to trigger + * other events on the rendered Liveboard. + * @version SDK: 1.9.1 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.LiveboardRendered, payload => { + console.log('Liveboard is rendered', payload); + }) + * ``` + * The following example shows how to trigger + * `SetVisibleVizs` event using LiveboardRendered embed event: + * @example + * ```js + * const embedRef = useEmbedRef(); + * const onLiveboardRendered = () => { + * embed.trigger(HostEvent.SetVisibleVizs, ['viz1', 'viz2']); + * }; + * ``` + */ + EmbedEvent["LiveboardRendered"] = "PinboardRendered"; + /** + * Emits all events. + * @version SDK: 1.10.0 | ThoughtSpot: 8.2.0.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.ALL, payload => { + * console.log('Embed Events', payload) + * }) + * ``` + */ + EmbedEvent["ALL"] = "*"; + /** + * Emitted when an Answer is saved in the app. + * Use start:true to subscribe to when save is initiated, or end:true to subscribe to when save is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //Emit when action starts + * searchEmbed.on(EmbedEvent.Save, payload => { + * console.log('Save', payload) + * }, { + * start: true + * }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.Save, payload => { + * console.log('Save', payload) + * }) + * ``` + */ + EmbedEvent["Save"] = "save"; + /** + * Emitted when the download action is triggered on an Answer. + * + * **Note**: This event is deprecated in v1.21.0. + * To fire an event when a download action is initiated on a chart or table, + * use `EmbedEvent.DownloadAsPng`, `EmbedEvent.DownloadAsPDF`, + * `EmbedEvent.DownloadAsCSV`, or `EmbedEvent.DownloadAsXLSX` + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Download, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }) + * ``` + */ + EmbedEvent["Download"] = "download"; + /** + * Emitted when the download action is triggered on an Answer. + * Use start:true to subscribe to when download is initiated, or end:true to + * subscribe to when download is completed. Default is end:true. + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.4.0.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.DownloadAsPng, payload => { + * console.log('download PNG', payload)}, {start: true }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.DownloadAsPng, payload => { + * console.log('download PNG', payload)}) + * ``` + */ + EmbedEvent["DownloadAsPng"] = "downloadAsPng"; + /** + * Emitted when the Download as PDF action is triggered on an Answer + * Use start:true to subscribe to when download as PDF is initiated, or end:true to + * subscribe to when download as PDF is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.DownloadAsPdf, payload => { + * console.log('download PDF', payload)}, {start: true }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.DownloadAsPdf, payload => { + * console.log('download PDF', payload)}) + * ``` + */ + EmbedEvent["DownloadAsPdf"] = "downloadAsPdf"; + /** + * Emitted when the Download as CSV action is triggered on an Answer. + * Use start:true to subscribe to when download as CSV is initiated, or end:true to + * subscribe to when download as CSV is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.DownloadAsCSV, payload => { + * console.log('download CSV', payload)}, {start: true }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.DownloadAsCSV, payload => { + * console.log('download CSV', payload)}) + * ``` + */ + EmbedEvent["DownloadAsCsv"] = "downloadAsCsv"; + /** + * Emitted when the Download as XLSX action is triggered on an Answer. + * Use start:true to subscribe to when download as XLSX is initiated, or end:true to + * subscribe to when download as XLSX is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.DownloadAsXlsx, payload => { + * console.log('download Xlsx', payload)}, { start: true }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.DownloadAsXlsx, payload => { + * console.log('download Xlsx', payload)}) + * ``` + */ + EmbedEvent["DownloadAsXlsx"] = "downloadAsXlsx"; + /** + * Emitted when the Download Liveboard as Continuous PDF action is triggered + * on a Liveboard. + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.DownloadLiveboardAsContinuousPDF, payload => { + * console.log('download liveboard as continuous PDF', payload)}) + * ``` + */ + EmbedEvent["DownloadLiveboardAsContinuousPDF"] = "downloadLiveboardAsContinuousPDF"; + /** + * Emitted when an Answer is deleted in the app + * Use start:true to subscribe to when delete is initiated, or end:true to subscribe + * to when delete is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * appEmbed.on(EmbedEvent.AnswerDelete, payload => { + * console.log('delete answer', payload)}, {start: true }) + * //trigger when action is completed + * appEmbed.on(EmbedEvent.AnswerDelete, payload => { + * console.log('delete answer', payload)}) + * ``` + */ + EmbedEvent["AnswerDelete"] = "answerDelete"; + /** + * Emitted when the AI Highlights action is triggered on a Liveboard + * @version SDK: 1.44.0 | ThoughtSpot: 10.15.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.AIHighlights, (payload) => { + * console.log('AI Highlights', payload); + * }) + * ``` + */ + EmbedEvent["AIHighlights"] = "AIHighlights"; + /** + * Emitted when a user initiates the Pin action to + * add an Answer to a Liveboard. + * Use start:true to subscribe to when pin is initiated, or end:true to subscribe to + * when pin is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.Pin, payload => { + * console.log('pin', payload) + * }, { + * start: true + * }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.Pin, payload => { + * console.log('pin', payload) + * }) + * ``` + */ + EmbedEvent["Pin"] = "pin"; + /** + * Emitted when SpotIQ analysis is triggered + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.SpotIQAnalyze, payload => { + * console.log('SpotIQAnalyze', payload) + * }, { + * start: true + * }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.SpotIQAnalyze, payload => { + * console.log('SpotIQ analyze', payload) + * }) + * ``` + */ + EmbedEvent["SpotIQAnalyze"] = "spotIQAnalyze"; + /** + * Emitted when a user shares an object with another user or group + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.Share, payload => { + * console.log('Share', payload) + * }, { + * start: true + * }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.Share, payload => { + * console.log('Share', payload) + * }) + * ``` + */ + EmbedEvent["Share"] = "share"; + /** + * Emitted when a user clicks the **Include** action to include a specific value or + * data on a chart or table. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.DrillInclude, payload => { + * console.log('Drill include', payload); + * }) + * ``` + */ + EmbedEvent["DrillInclude"] = "context-menu-item-include"; + /** + * Emitted when a user clicks the **Exclude** action to exclude a specific value or + * data on a chart or table + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.DrillExclude, payload => { + * console.log('Drill exclude', payload); + * }) + * ``` + */ + EmbedEvent["DrillExclude"] = "context-menu-item-exclude"; + /** + * Emitted when a column value is copied in the embedded app. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * searchEmbed.on(EmbedEvent.CopyToClipboard, payload => { + * console.log('copy to clipboard', payload); + * }) + * ``` + */ + EmbedEvent["CopyToClipboard"] = "context-menu-item-copy-to-clipboard"; + /** + * Emitted when a user clicks the **Update TML** action on + * embedded Liveboard. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.UpdateTML) + * }) + * ``` + */ + EmbedEvent["UpdateTML"] = "updateTSL"; + /** + * Emitted when a user clicks the **Edit TML** action + * on an embedded Liveboard. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.EditTML, payload => { + * console.log('Edit TML', payload); + * }) + * ``` + */ + EmbedEvent["EditTML"] = "editTSL"; + /** + * Emitted when the **Export TML** action is triggered on an + * an embedded object in the app + * Use start:true to subscribe to when export is initiated, or end:true to subscribe + * to when export is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.ExportTML, payload => { + * console.log('Export TML', payload)}, { start: true }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.ExportTML, payload => { + * console.log('Export TML', payload)}) + * ``` + */ + EmbedEvent["ExportTML"] = "exportTSL"; + /** + * Emitted when an Answer is saved as a View. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.SaveAsView, payload => { + * console.log('View', payload); + * }) + * ``` + */ + EmbedEvent["SaveAsView"] = "saveAsView"; + /** + * Emitted when the user creates a copy of an Answer. + * Use start:true to subscribe to when copy and edit is initiated, or end:true to + * subscribe to when copy and edit is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * appEmbed.on(EmbedEvent.CopyAEdit, payload => { + * console.log('Copy and edit', payload)}, {start: true }) + * //emit when action ends + * appEmbed.on(EmbedEvent.CopyAEdit, payload => { + * console.log('Copy and edit', payload)}) + * ``` + */ + EmbedEvent["CopyAEdit"] = "copyAEdit"; + /** + * Emitted when a user clicks *Show underlying data* on an Answer. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.ShowUnderlyingData, payload => { + * console.log('show data', payload); + * }) + * ``` + */ + EmbedEvent["ShowUnderlyingData"] = "showUnderlyingData"; + /** + * Emitted when an Answer is switched to a chart or table view. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * searchEmbed.on(EmbedEvent.AnswerChartSwitcher, payload => { + * console.log('switch view', payload); + * }) + * ``` + */ + EmbedEvent["AnswerChartSwitcher"] = "answerChartSwitcher"; + /** + * Internal event to communicate the initial settings back to the ThoughtSpot app + * @hidden + */ + EmbedEvent["APP_INIT"] = "appInit"; + /** + * Internal event to clear the cached info + * @hidden + */ + EmbedEvent["CLEAR_INFO_CACHE"] = "clearInfoCache"; + /** + * Emitted when a user clicks **Show Liveboard details** on a Liveboard + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.LiveboardInfo, payload => { + * console.log('Liveboard details', payload); + * }) + * ``` + */ + EmbedEvent["LiveboardInfo"] = "pinboardInfo"; + /** + * Emitted when a user clicks on the Favorite icon on a Liveboard + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.AddToFavorites, payload => { + * console.log('favorites', payload); + * }) + * ``` + */ + EmbedEvent["AddToFavorites"] = "addToFavorites"; + /** + * Emitted when a user clicks **Schedule** on a Liveboard + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Schedule, payload => { + * console.log('Liveboard schedule', payload); + * }) + * ``` + */ + EmbedEvent["Schedule"] = "subscription"; + /** + * Emitted when a user clicks **Edit** on a Liveboard or visualization + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Edit, payload => { + * console.log('Liveboard edit', payload); + * }) + * ``` + */ + EmbedEvent["Edit"] = "edit"; + /** + * Emitted when a user clicks *Make a copy* on a Liveboard + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.MakeACopy, payload => { + * console.log('Copy', payload); + * }) + * ``` + */ + EmbedEvent["MakeACopy"] = "makeACopy"; + /** + * Emitted when a user clicks **Present** on a Liveboard or visualization + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Present) + * ``` + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Present, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * }) + * ``` + */ + EmbedEvent["Present"] = "present"; + /** + * Emitted when a user clicks **Delete** on a visualization + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Delete, + * {vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + */ + EmbedEvent["Delete"] = "delete"; + /** + * Emitted when a user clicks Manage schedules on a Liveboard + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SchedulesList) + * ``` + */ + EmbedEvent["SchedulesList"] = "schedule-list"; + /** + * Emitted when a user clicks **Cancel** in edit mode on a Liveboard + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Cancel) + * ``` + */ + EmbedEvent["Cancel"] = "cancel"; + /** + * Emitted when a user clicks **Explore** on a visualization + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Explore, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + */ + EmbedEvent["Explore"] = "explore"; + /** + * Emitted when a user clicks **Copy link** action on a visualization. + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.CopyLink, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + */ + EmbedEvent["CopyLink"] = "embedDocument"; + /** + * Emitted when a user interacts with cross filters on a + * visualization or Liveboard. + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.0.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.CrossFilterChanged, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + */ + EmbedEvent["CrossFilterChanged"] = "cross-filter-changed"; + /** + * Emitted when a user right clicks on a visualization (chart or table) + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.0.sw + * @example + * ```js + * LiveboardEmbed.on(EmbedEvent.VizPointRightClick, payload => { + * console.log('VizPointClick', payload) + * }) + * ``` + */ + EmbedEvent["VizPointRightClick"] = "vizPointRightClick"; + /** + * Emitted when a user clicks **Insert to slide** on a visualization + * @hidden + */ + EmbedEvent["InsertIntoSlide"] = "insertInToSlide"; + /** + * Emitted when a user changes any filter on a Liveboard. + * Returns filter type and name, column name and ID, and runtime + * filter details. + * @example + * + * ```js + * LiveboardEmbed.on(EmbedEvent.FilterChanged, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.23.0 | ThoughtSpot: 9.4.0.cl, 9.5.0.sw + */ + EmbedEvent["FilterChanged"] = "filterChanged"; + /** + * Emitted when a user updates a connection on the **Data** page + * @version SDK: 1.27.0 | ThoughtSpot: 9.8.0.cl, 9.8.0.sw + */ + EmbedEvent["UpdateConnection"] = "updateConnection"; + /** + * Emitted when a user updates a connection on the **Data** page + * @version SDK: 1.27.0 | ThoughtSpot: 9.8.0.cl, 9.8.0.sw + */ + EmbedEvent["CreateConnection"] = "createConnection"; + /** + * Emitted when name, status (private or public) or filter values of a + * Personalised view is updated. + * This event is deprecated. Use {@link EmbedEvent.UpdatePersonalizedView} instead. + * @returns viewName: string + * @returns viewId: string + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + EmbedEvent["UpdatePersonalisedView"] = "updatePersonalisedView"; + /** + * Emitted when name, status (private or public) or filter values of a + * Personalized view is updated. + * @returns viewName: string + * @returns viewId: string + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + EmbedEvent["UpdatePersonalizedView"] = "updatePersonalisedView"; + /** + * Emitted when a Personalised view is saved. + * This event is deprecated. Use {@link EmbedEvent.SavePersonalizedView} instead. + * @returns viewName: string + * @returns viewId: string + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + EmbedEvent["SavePersonalisedView"] = "savePersonalisedView"; + /** + * Emitted when a Personalized view is saved. + * @returns viewName: string + * @returns viewId: string + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + EmbedEvent["SavePersonalizedView"] = "savePersonalisedView"; + /** + * Emitted when a Liveboard is reset. + * @returns viewName: string + * @returns viewId: string + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + */ + EmbedEvent["ResetLiveboard"] = "resetLiveboard"; + /** + * Emitted when a PersonalisedView is deleted. + * This event is deprecated. Use {@link EmbedEvent.DeletePersonalizedView} instead. + * @returns views: string[] + * @returns liveboardId: string + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + EmbedEvent["DeletePersonalisedView"] = "deletePersonalisedView"; + /** + * Emitted when a PersonalizedView is deleted. + * @returns views: string[] + * @returns liveboardId: string + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + EmbedEvent["DeletePersonalizedView"] = "deletePersonalisedView"; + /** + * Emitted when a user selects a different Personalized View or + * resets to the original/default view on a Liveboard. + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.ChangePersonalizedView, (data) => { + * console.log(data.viewName); // 'Q4 Revenue' or 'Original View' + * console.log(data.viewId); // '2a021a12-...' or null (default) + * console.log(data.liveboardId); // 'abc123...' + * console.log(data.isPublic); // true | false + * }) + * ``` + * @returns viewName: string - Name of the selected view, + * or 'Original View' when reset to default. + * @returns viewId: string | null - GUID of the selected view, + * or null when reset to default. + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + EmbedEvent["ChangePersonalizedView"] = "changePersonalisedView"; + /** + * Emitted when a user creates a Worksheet. + * @version SDK: 1.27.0 | ThoughtSpot: 9.8.0.cl, 9.8.0.sw + */ + EmbedEvent["CreateWorksheet"] = "createWorksheet"; + /** + * Emitted when the *Ask Sage* is initialized. + * @returns viewName: string + * @returns viewId: string + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.29.0 | ThoughtSpot Cloud: 9.12.0.cl + */ + EmbedEvent["AskSageInit"] = "AskSageInit"; + /** + * Emitted when a Liveboard or visualization is renamed. + * @version SDK: 1.28.0 | ThoughtSpot: 9.10.5.cl, 10.1.0.sw + */ + EmbedEvent["Rename"] = "rename"; + /** + * + * This event allows developers to intercept search execution + * and implement logic that decides whether Search Data should return + * data or block the search operation. + * + * **Prerequisite**: Set`isOnBeforeGetVizDataInterceptEnabled` to `true` + * to ensure that `EmbedEvent.OnBeforeGetVizDataIntercept` is emitted + * when the embedding application user tries to run a search query. + * + * This framework applies only to `AppEmbed` and `SearchEmbed`. + * @param - Includes the following parameters: + * - `payload`: The payload received from the embed related to the Data API call. + * - `responder`: Contains elements that let developers define whether ThoughtSpot + * will run or block the search operation, and if blocked, which error message to + * provide. + * - `execute` - When `execute` returns `true`, the search is run. + * When `execute` returns `false`, the search is not executed. + * - `error` - Developers can customize the user-facing error message when `execute` + * is `false` by using the `error` parameters in `responder`. + * - `errorText` - The error message text shown to the user. + * @version SDK: 1.29.0 | ThoughtSpot: 10.3.0.cl + * @example + * + * This example blocks search operation and returns a custom error message: + * ```js + * embed.on(EmbedEvent.OnBeforeGetVizDataIntercept, (payload, responder) => { + * responder({ + * data: { + * execute: false, + * error: { + * // Provide a custom error message to explain why the search did not run. + * errorText: 'This search query cannot be run. Please contact your administrator for more details.', + * }, + * }, + * }); + * }) + * ``` + * @example + * + * This example allows the search operation to run + * unless the query contains both `sales` and `county`, + * and returns a custom error message if the query is rejected: + * ```js + * embed.on(EmbedEvent.OnBeforeGetVizDataIntercept, (payload, responder) => { + * // Record the search query submitted by the end user. + * const query = payload.data.data.answer.search_query; + * + * responder({ + * data: { + * // Returns true as long as the query does not include both `sales` and `county`. + * execute: !(query.includes('sales') && query.includes('county')), + * error: { + * // Provide a custom error message when the query is blocked by your logic. + * errorText: + * "You can't use this query: " + * + query + * + ". The 'sales' measure can never be used at the 'county' level. " + * + "Please try another measure or remove 'county' from your search.", + * }, + * }, + * }); + * }) + * ``` + */ + EmbedEvent["OnBeforeGetVizDataIntercept"] = "onBeforeGetVizDataIntercept"; + /** + * Emitted when parameter changes in an Answer + * or Liveboard. + * ```js + * liveboardEmbed.on(EmbedEvent.ParameterChanged, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.29.0 | ThoughtSpot: 10.3.0.cl + */ + EmbedEvent["ParameterChanged"] = "parameterChanged"; + /** + * Emits when a table visualization is rendered in + * the ThoughtSpot embedded app. + * + * You can also use this event as a hook to trigger host events + * such as `HostEvent.TransformTableVizData` on the table visualization. + * The event payload contains the data used in the rendered table. + * You can extract the relevant data from the payload + * stored in `payload.data.data.columnDataLite`. + * + * `columnDataLite` is a multidimensional array that contains + * data values for each column, which was used in the query to + * generate the table visualization. To find and modify specific cell data, + * you can either loop through the array or directly access a cell if + * you know its position and data index. + * + * In the following code sample, the first cell in the first column + * (`columnDataLite[0].dataValue[0]`) is set to `new fob`. + * Note that any changes made to the data in the payload will only update the + * visual presentation and do not affect the underlying data. + * To persist data value modifications after a reload or during chart + * interactions such as drill down, ensure that the modified + * payload in the `columnDataLite` is passed on to + * `HostEvent.TransformTableVizData` and trigger an update to + * the table visualization. + * + * If the Row-Level Security (RLS) rules are applied on the + * Model, exercise caution when changing column + * or table cell values to maintain data security. + * + * @example + * ```js + * searchEmbed.on(EmbedEvent.TableVizRendered, (payload) => { + * console.log(payload); + * const columnDataLite = payload.data.data.columnDataLite; + * columnDataLite[0].dataValue[0]="new fob"; + * console.log('>>> new Data', columnDataLite); + * searchEmbed.trigger(HostEvent.TransformTableVizData, columnDataLite); + * }) + * ``` + * @version SDK: 1.37.0 | ThoughtSpot: 10.8.0.cl + */ + EmbedEvent["TableVizRendered"] = "TableVizRendered"; + /** + * Emitted when the liveboard is created from pin modal or Liveboard list page. + * You can use this event as a hook to trigger + * other events on liveboard creation. + * + * ```js + * liveboardEmbed.on(EmbedEvent.CreateLiveboard, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.37.0 | ThoughtSpot: 10.8.0.cl + */ + EmbedEvent["CreateLiveboard"] = "createLiveboard"; + /** + * Emitted when a user creates a Model. + * @version SDK: 1.37.0 | ThoughtSpot: 10.8.0.cl + */ + EmbedEvent["CreateModel"] = "createModel"; + /** + * @hidden + * Emitted when a user exits present mode. + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + EmbedEvent["ExitPresentMode"] = "exitPresentMode"; + /** + * Emitted when a user requests the full height lazy load data. + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + * @hidden + */ + EmbedEvent["RequestVisibleEmbedCoordinates"] = "requestVisibleEmbedCoordinates"; + /** + * Emitted when Spotter response is text data + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterData, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + EmbedEvent["SpotterData"] = "SpotterData"; + /** + * Emitted when user opens up the data source preview modal in Spotter embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.PreviewSpotterData, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + EmbedEvent["PreviewSpotterData"] = "PreviewSpotterData"; + /** + * Emitted when user opens up the Add to Coaching modal on any visualization in Spotter Embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.AddToCoaching, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.45.0 | ThoughtSpot: 26.2.0.cl + */ + EmbedEvent["AddToCoaching"] = "addToCoaching"; + /** + * Emitted when user opens up the data model instructions modal in Spotter embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.DataModelInstructions, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + */ + EmbedEvent["DataModelInstructions"] = "DataModelInstructions"; + /** + * Emitted when the Spotter query is triggered in Spotter embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterQueryTriggered, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + EmbedEvent["SpotterQueryTriggered"] = "SpotterQueryTriggered"; + /** + * Emitted when the last Spotter query is edited in Spotter embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.LastPromptEdited, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + EmbedEvent["LastPromptEdited"] = "LastPromptEdited"; + /** + * Emitted when the last Spotter query is deleted in Spotter embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.LastPromptDeleted, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + EmbedEvent["LastPromptDeleted"] = "LastPromptDeleted"; + /** + * Emitted when the conversation is reset in Spotter embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.ResetSpotterConversation, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + EmbedEvent["ResetSpotterConversation"] = "ResetSpotterConversation"; + /** + * Emitted when the *Spotter* is initialized. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterInit, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.41.0 | ThoughtSpot: 10.12.0.cl + */ + EmbedEvent["SpotterInit"] = "spotterInit"; + /** + * Emitted when a *Spotter* conversation has been successfully created. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterLoadComplete, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.44.0 | ThoughtSpot: 26.2.0.cl + */ + EmbedEvent["SpotterLoadComplete"] = "spotterLoadComplete"; + /** + * @hidden + * Triggers when the embed listener is ready to receive events. + * This is used to trigger events after the embed container is loaded. + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.EmbedListenerReady, () => { + * console.log('EmbedListenerReady'); + * }) + * ``` + */ + EmbedEvent["EmbedListenerReady"] = "EmbedListenerReady"; + /** + * Emitted when the organization is switched. + * @example + * ```js + * appEmbed.on(EmbedEvent.OrgSwitched, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.41.0 | ThoughtSpot: 10.12.0.cl + */ + EmbedEvent["OrgSwitched"] = "orgSwitched"; + /** + * Emitted when the user intercepts a URL. + * + * Supported on all embed types. + * + * @example + * + * ```js + * embed.on(EmbedEvent.ApiIntercept, (payload, responder) => { + * console.log('payload', payload); + * responder({ + * data: { + * execute: false, + * error: { + * errorText: 'Error Occurred', + * } + * } + * }) + * }) + * ``` + * + * ```js + * // We can also send a response for the intercepted api + * embed.on(EmbedEvent.ApiIntercept, (payload, responder) => { + * console.log('payload', payload); + * responder({ + * data: { + * execute: false, + * response: { + * body: { + * data: { + * // Some api response + * }, + * } + * } + * } + * }) + * }) + * + * // here embed will use the response from the responder as the response for the api + * ``` + * + * ```js + * // We can also send error in response for the intercepted api + * embed.on(EmbedEvent.ApiIntercept, (payload, responder) => { + * console.log('payload', payload); + * responder({ + * data: { + * execute: false, + * response: { + * body: { + * errors: [{ + * title: 'Error Occurred', + * description: 'Error Description', + * isUserError: true, + * }], + * data: {}, + * }, + * } + * } + * }) + * }) + * ``` + * @version SDK: 1.43.0 | ThoughtSpot: 10.15.0.cl + */ + EmbedEvent["ApiIntercept"] = "ApiIntercept"; + /** + * Emitted when a Spotter conversation is renamed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterConversationRenamed, (payload) => { + * console.log('Conversation renamed', payload); + * // payload: { convId: string, oldTitle: string, newTitle: string } + * }) + * ``` + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + */ + EmbedEvent["SpotterConversationRenamed"] = "spotterConversationRenamed"; + /** + * Emitted when a Spotter conversation is deleted. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterConversationDeleted, (payload) => { + * console.log('Conversation deleted', payload); + * // payload: { convId: string, title: string } + * }) + * ``` + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + */ + EmbedEvent["SpotterConversationDeleted"] = "spotterConversationDeleted"; + /** + * Emitted when a Spotter conversation is selected/clicked. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterConversationSelected, (payload) => { + * console.log('Conversation selected', payload); + * // payload: { convId: string, title: string, worksheetId: string } + * }) + * ``` + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + */ + EmbedEvent["SpotterConversationSelected"] = "spotterConversationSelected"; + /** + * @hidden + * Emitted when the auth token is about to get expired and needs to be refreshed. + * @example + * ```js + * embed.on(EmbedEvent.RefreshAuthToken, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.45.2 | ThoughtSpot: 26.3.0.cl + */ + EmbedEvent["RefreshAuthToken"] = "RefreshAuthToken"; + /** + * Triggered whenever the page context changes, returning the current context along with the navigation stack. + * @example + * ```js + * embed.on(EmbedEvent.EmbedPageContextChanged, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.47.2 | ThoughtSpot: 26.3.0.cl + */ + EmbedEvent["EmbedPageContextChanged"] = "EmbedPageContextChanged"; + /** + * Represents a special embed event that is triggered whenever any host event is subscribed. + * + * You can listen to this event when you need to dispatch a host event during load or render, + * particularly in situations where timing issues may occur. + * + * @example + * ```js + * embed.on(`${HostEvent.Save} Subscribed`, () => { + * // make action + * }); + * ``` + * + * @example + * ```js + * embed.on(subscribedEvent(HostEvent.Save), () => { + * // make action + * }); + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.4.0.cl + */ + EmbedEvent["Subscribed"] = "Subscribed"; + /** + * Emitted when a user clicks the **Send Test Email** button in the + * Liveboard schedule modal. Requires `isSendNowLiveboardSchedulingEnabled` + * to be enabled. + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SendTestScheduleEmail, (payload) => { + * console.log('Send test email', payload); + * // payload: { liveboardId: string, sendToSelf: boolean } + * }) + * ``` + * @version SDK: 1.48.0 | ThoughtSpot Cloud: 26.5.0.cl + */ + EmbedEvent["SendTestScheduleEmail"] = "sendTestScheduleEmail"; + /** + * Emitted when the SpotterViz panel mounts in embed mode. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizInit, (payload) => { + * console.log('SpotterViz initialized', payload); + * // payload: { liveboardId: string } + * }) + * ``` + */ + EmbedEvent["SpotterVizInit"] = "SpotterVizInit"; + /** + * Emitted when the user submits a prompt in the SpotterViz panel. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizQueryTriggered, (payload) => { + * console.log('SpotterViz query triggered', payload); + * // payload: { query: string, sessionId: string } + * }) + * ``` + */ + EmbedEvent["SpotterVizQueryTriggered"] = "SpotterVizQueryTriggered"; + /** + * Emitted when the SpotterViz agent finishes responding. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizResponseComplete, (payload) => { + * console.log('SpotterViz response complete', payload); + * // payload: { sessionId: string, messageId: string } + * }) + * ``` + */ + EmbedEvent["SpotterVizResponseComplete"] = "SpotterVizResponseComplete"; + /** + * Emitted when a checkpoint is created in the SpotterViz panel. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizCheckpointCreated, (payload) => { + * console.log('SpotterViz checkpoint created', payload); + * // payload: { checkpointId: string, source: string, label: string } + * }) + * ``` + */ + EmbedEvent["SpotterVizCheckpointCreated"] = "SpotterVizCheckpointCreated"; + /** + * Emitted when a checkpoint is restored in the SpotterViz panel. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizCheckpointRestored, (payload) => { + * console.log('SpotterViz checkpoint restored', payload); + * // payload: { checkpointId: string, newGenNumber: number } + * }) + * ``` + */ + EmbedEvent["SpotterVizCheckpointRestored"] = "SpotterVizCheckpointRestored"; + /** + * Emitted when an error occurs in the SpotterViz panel. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizError, (payload) => { + * console.log('SpotterViz error', payload); + * }) + * ``` + */ + EmbedEvent["SpotterVizError"] = "SpotterVizError"; + /** + * Emitted when the SpotterViz panel is closed. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizClosed, (payload) => { + * console.log('SpotterViz panel closed', payload); + * }) + * ``` + */ + EmbedEvent["SpotterVizClosed"] = "SpotterVizClosed"; + /** + * Emitted when a user clicks the **Refresh** button in the + * Liveboard header. Requires `enableLiveboardDataCache` + * to be enabled. + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.RefreshLiveboardBrowserCache, (payload) => { + * console.log('Liveboard browser cache refreshed', payload); + * // payload: { liveboardId: string } + * }) + * ``` + * @version SDK: 1.49.0 | ThoughtSpot Cloud: 26.6.0.cl + */ + EmbedEvent["RefreshLiveboardBrowserCache"] = "refreshLiveboardBrowserCache"; + })(exports.EmbedEvent || (exports.EmbedEvent = {})); + /** + * Event types that can be triggered by the host application + * to the embedded ThoughtSpot app. + * + * To trigger an event use the corresponding + * {@link LiveboardEmbed.trigger} or {@link AppEmbed.trigger} or {@link + * SearchEmbed.trigger} method. + * @example + * ```js + * import { HostEvent } from '@thoughtspot/visual-embed-sdk'; + * // Or + * // const { HostEvent } = window.tsembed; + * + * // create the liveboard embed. + * + * liveboardEmbed.trigger(HostEvent.UpdateRuntimeFilters, [ + * { columnName: 'state', operator: RuntimeFilterOp.EQ, values: ["california"]} + * ]); + * ``` + * @example + * If using React components to embed, use the format shown in this example: + * + * ```js + * const selectVizs = () => { + * embedRef.current.trigger(HostEvent.SetVisibleVizs, [ + * "715e4613-c891-4884-be44-aa8d13701c06", + * "3f84d633-e325-44b2-be25-c6650e5a49cf" + * ]); + * }; + * ``` + * + * + * You can also attach an Embed event to a Host event to trigger + * a specific action as shown in this example: + * @example + * ```js + * const EmbeddedComponent = () => { + * const embedRef = useRef(null); // import { useRef } from react + * const onLiveboardRendered = () => { + * embedRef.current.trigger(HostEvent.SetVisibleVizs, ['viz1', 'viz2']); + * }; + * + * return ( + * + * ); + * } + * ``` + * + * **Context Parameter (SDK: 1.45.2+)** + * + * Starting from SDK version 1.45.2 | ThoughtSpot: 26.3.0.cl, you can optionally pass a + * `ContextType` as the third parameter to the `trigger` method to specify the context + * from which the event is triggered. This helps ThoughtSpot understand the current page + * context (Search, Answer, Liveboard, or Spotter) for better event handling. + * + * @example + * ```js + * import { HostEvent, ContextType } from '@thoughtspot/visual-embed-sdk'; + * + * // Trigger Pin event with Search context + * appEmbed.trigger(HostEvent.Pin, { + * vizId: "123", + * liveboardId: "456" + * }, ContextType.Search); + * ``` + * + * @group Events + */ + exports.HostEvent = void 0; + (function (HostEvent) { + /** + * Triggers a search operation with the search tokens specified in + * the search query string. + * Supported in `AppEmbed` and `SearchEmbed` deployments. + * Includes the following properties: + * @param - Includes the following keys: + * - `searchQuery`: Query string with search tokens. + * - `dataSources`: Data source GUID to search on. + * Although an array, only a single source is supported. + * - `execute`: Executes search and updates the existing query. + * @example + * ```js + * searchEmbed.trigger(HostEvent.Search, { + searchQuery: "[sales] by [item type]", + dataSources: ["cd252e5c-b552-49a8-821d-3eadaa049cca"], + execute: true + }); + * ``` + * @example + * ```js + * // Trigger search from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Search, { + * searchQuery: "[revenue] by [region]", + * dataSources: ["cd252e5c-b552-49a8-821d-3eadaa049cca"], + * execute: true + * }, ContextType.Search); + * ``` + */ + HostEvent["Search"] = "search"; + /** + * Triggers a drill on certain points of the specified column + * Includes the following properties: + * @param - Includes the following keys: + * - `points`: An object containing `selectedPoints` and/or `clickedPoint` + * to drill to. For example, `{ selectedPoints: [] }`. + * - `columnGuid`: Optional. GUID of the column to drill by. If not provided, + * it will auto drill by the configured column. + * - `autoDrillDown`: Optional. If `true`, the drill down will be done automatically + * on the most popular column. + * - `vizId` (TS >= 9.8.0): Optional. The GUID of the visualization to drill in case + * of a Liveboard. In Spotter embed, `vizId` refers to the Answer ID and is + * **required**. + * @example + * ```js + * searchEmbed.on(EmbedEvent.VizPointDoubleClick, (payload) => { + * console.log(payload); + * const clickedPoint = payload.data.clickedPoint; + * const selectedPoint = payload.data.selectedPoints; + * console.log('>>> called', clickedPoint); + * searchEmbed.trigger(HostEvent.DrillDown, { + * points: { + * clickedPoint, + * selectedPoints: selectedPoint + * }, + * autoDrillDown: true, + * }); + * }) + * ``` + * @example + * ```js + * // Works with TS 9.8.0 and above + * + * liveboardEmbed.on(EmbedEvent.VizPointDoubleClick, (payload) => { + * console.log(payload); + * const clickedPoint = payload.data.clickedPoint; + * const selectedPoint = payload.data.selectedPoints; + * console.log('>>> called', clickedPoint); + * liveboardEmbed.trigger(HostEvent.DrillDown, { + * points: { + * clickedPoint, + * selectedPoints: selectedPoint + * }, + * columnGuid: "", + * vizId: payload.data.vizId + * }); + * }) + * ``` + * @example + * ```js + * // Drill down from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.DrillDown, { + * points: { clickedPoint, selectedPoints }, + * autoDrillDown: true + * }, ContextType.Answer); + * ``` + * @example + * ```js + * // Drill down from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.DrillDown, { + * points: { clickedPoint, selectedPoints }, + * columnGuid: "column-guid" + * }, ContextType.Search); + * ``` + * @version SDK: 1.5.0 | ThoughtSpot: ts7.oct.cl, 7.2.1 + */ + HostEvent["DrillDown"] = "triggerDrillDown"; + /** + * Apply filters + * @hidden + */ + HostEvent["Filter"] = "filter"; + /** + * Reload the Answer or visualization + * @hidden + */ + HostEvent["Reload"] = "reload"; + /** + * Get iframe URL for the current embed view. + * @example + * ```js + * const url = embed.trigger(HostEvent.GetIframeUrl); + * console.log("iFrameURL",url); + * ``` + * @example + * ```js + * // Get iframe URL from specific context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * const url = await appEmbed.trigger(HostEvent.GetIframeUrl, {}, ContextType.Answer); + * console.log("iFrameURL", url); + * ``` + * @version SDK: 1.35.0 | ThoughtSpot: 10.4.0.cl + */ + HostEvent["GetIframeUrl"] = "GetIframeUrl"; + /** + * Display specific visualizations on a Liveboard. + * @param - An array of GUIDs of the visualization to show. The visualization IDs not passed + * in this parameter will be hidden. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SetVisibleVizs, [ + * '730496d6-6903-4601-937e-2c691821af3c', + * 'd547ec54-2a37-4516-a222-2b06719af726']) + * ``` + * @example + * ```js + * // Set visible vizs from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SetVisibleVizs, [ + * '730496d6-6903-4601-937e-2c691821af3c', + * 'd547ec54-2a37-4516-a222-2b06719af726' + * ], ContextType.Liveboard); + * ``` + * @version SDK: 1.6.0 | ThoughtSpot: ts8.nov.cl, 8.4.1.sw + */ + HostEvent["SetVisibleVizs"] = "SetPinboardVisibleVizs"; + /** + * Set a Liveboard tab as an active tab. + * @param - tabId - string of id of Tab to show + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SetActiveTab,{ + * tabId:'730496d6-6903-4601-937e-2c691821af3c' + * }) + * ``` + * @example + * ```js + * // Set active tab from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SetActiveTab, { + * tabId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.24.0 | ThoughtSpot: 9.5.0.cl, 9.5.1-sw + */ + HostEvent["SetActiveTab"] = "SetActiveTab"; + /** + * Updates the runtime filters applied on a Liveboard. The filter + * attributes passed with this event are appended to the existing runtime + * filters applied on a Liveboard. + * + * **Note**: `HostEvent.UpdateRuntimeFilters` is supported in `LiveboardEmbed` + * and `AppEmbed` only. In full application embedding, this event updates + * the runtime filters applied on the Liveboard and saved Answer objects. + * + * @param - Array of {@link RuntimeFilter} objects. Each item includes: + * - `columnName`: Name of the column to filter on. + * - `operator`: {@link RuntimeFilterOp} to apply. For more information, see + * link:https://developers.thoughtspot.com/docs/runtime-filters#rtOperator[Developer Documentation]. + * - `values`: List of operands. Some operators such as EQ and LE allow a single + * value, whereas BW and IN accept multiple values. + * + * **Note**: Updating runtime filters resets the ThoughtSpot + * object to its original state and applies new filter conditions. + * Any user changes (like drilling into a visualization) + * will be cleared, restoring the original visualization + * with the updated filters. + * + + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateRuntimeFilters, [ + * {columnName: "state",operator: RuntimeFilterOp.EQ,values: ["michigan"]}, + * {columnName: "item type",operator: RuntimeFilterOp.EQ,values: ["Jackets"]} + * ]) + * ``` + * @example + * ```js + * // Update runtime filters from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.UpdateRuntimeFilters, [ + * {columnName: "region", operator: RuntimeFilterOp.EQ, values: ["west"]}, + * {columnName: "product", operator: RuntimeFilterOp.IN, values: ["shoes", "boots"]} + * ], ContextType.Liveboard); + * ``` + * @version SDK: 1.9.0 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + * @important + */ + HostEvent["UpdateRuntimeFilters"] = "UpdateRuntimeFilters"; + /** + * Navigate to a specific page in the embedded ThoughtSpot application. + * This is the same as calling `appEmbed.navigateToPage(path, true)`. + * @param - `path` - the path to navigate to go forward or back. The path value can + * be a number; for example, `1`, `-1`. + * @example + * ```js + * appEmbed.navigateToPage(-1) + * ``` + * @version SDK: 1.12.0 | ThoughtSpot: 8.4.0.cl, 8.4.1.sw + */ + HostEvent["Navigate"] = "Navigate"; + /** + * Open the filter panel for a particular column. + * Works with Search and Liveboard embed. + * @param - { columnId: string, + * name: string, + * type: ATTRIBUTE/MEASURE, + * dataType: INT64/CHAR/DATE } + * @example + * ```js + * searchEmbed.trigger(HostEvent.OpenFilter, + * {column: { columnId: '', name: 'column name', type: 'ATTRIBUTE', dataType: 'INT64'}}) + * ``` + * @example + * ```js + * LiveboardEmbed.trigger(HostEvent.OpenFilter, + * { column: {columnId: ''}}) + * ``` + * @example + * ```js + * // Open filter from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.OpenFilter, { + * column: { columnId: '', name: 'region', type: 'ATTRIBUTE', dataType: 'CHAR'} + * }, ContextType.Search); + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl + */ + HostEvent["OpenFilter"] = "openFilter"; + /** + * Add columns to the current search query. + * @param - { columnIds: string[] } + * @example + * ```js + * searchEmbed.trigger(HostEvent.AddColumns, { columnIds: ['',''] }) + * ``` + * @example + * ```js + * // Add columns from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.AddColumns, { + * columnIds: ['col-guid-1', 'col-guid-2'] + * }, ContextType.Search); + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl + */ + HostEvent["AddColumns"] = "addColumns"; + /** + * Remove a column from the current search query. + * @param - { columnId: string } + * @example + * ```js + * searchEmbed.trigger(HostEvent.RemoveColumn, { columnId: '' }) + * ``` + * @example + * ```js + * // Remove column from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.RemoveColumn, { + * columnId: 'column-guid' + * }, ContextType.Search); + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl + */ + HostEvent["RemoveColumn"] = "removeColumn"; + /** + * Get the transient state of a Liveboard as encoded content. + * This includes unsaved and ad hoc changes such as + * Liveboard filters, runtime filters applied on visualizations on a + * Liveboard, and Liveboard layout, changes to visualizations such as + * sorting, toggling of legends, and data drill down. + * For more information, see + * link:https://developers.thoughtspot.com/docs/fetch-data-and-report-apis#transient-lb-content[Liveboard data with unsaved changes]. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.getExportRequestForCurrentPinboard).then( + * data=>console.log(data)) + * ``` + * @version SDK: 1.13.0 | ThoughtSpot: 8.5.0.cl, 8.8.1.sw + */ + HostEvent["getExportRequestForCurrentPinboard"] = "getExportRequestForCurrentPinboard"; + /** + * Trigger **Pin** action on an embedded object. + * If no parameters are defined, the pin action is triggered + * for the Answer that the user is currently on + * and a modal opens for Liveboard selection. + * To add an Answer or visualization to a Liveboard programmatically without + * requiring additional user input via the *Pin to Liveboard* modal, define + * the following parameters: + * + * @param - Includes the following keys: + * - `vizId`: GUID of the saved Answer or Spotter visualization ID to pin to a + * Liveboard. + * Optional when pinning a new chart or table generated from a Search query. + * **Required** in Spotter Embed. + * - `liveboardId`: GUID of the Liveboard to pin an Answer. If there is no Liveboard, + * specify the `newLiveboardName` parameter to create a new Liveboard. + * - `tabId`: GUID of the Liveboard tab. Adds the Answer to the Liveboard tab + * specified in the code. + * - `newVizName`: Name string for the Answer or visualization. If defined, + * this parameter adds a new visualization object or creates a copy of the + * Answer or visualization specified in `vizId`. + * Required. + * - `newLiveboardName`: Name string for the Liveboard. + * Creates a new Liveboard object with the specified name. + * - `newTabName`: Name of the tab. Adds a new tab Liveboard specified + * in the code. + * + * @example + * ```js + * const pinResponse = await appEmbed.trigger(HostEvent.Pin, { + * vizId: "123", + * newVizName: "Sales by region", + * liveboardId: "123", + * tabId: "123" + * }); + * ``` + * @example + * ```js + * const pinResponse = await appEmbed.trigger(HostEvent.Pin, { + * newVizName: "Total sales of Jackets", + * liveboardId: "123" + * }); + * ``` + * + * @example + * ```js + * const pinResponse = await searchEmbed.trigger(HostEvent.Pin, { + * newVizName: "Sales by state", + * newLiveboardName: "Sales", + * newTabName: "Products" + * }); + * ``` + * @example + * ```js + * appEmbed.trigger(HostEvent.Pin) + * ``` + * @example + * ```js + + * // You can use the Data event dispatched on each answer creation to get the vizId and use in Pin host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.Pin, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Using context parameter to specify the context type (SDK: 1.45.2+) + * // Pin from a search answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Pin, { + * vizId: "123", + * newVizName: "Sales by region", + * liveboardId: "456" + * }, ContextType.Search); + * ``` + * @example + * ```js + * // Pin from an answer context (explore modal/page) (SDK: 1.45.2+) + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Pin, { + * vizId: "789", + * newVizName: "Revenue trends", + * liveboardId: "456" + * }, ContextType.Answer); + * ``` + * @example + * ```js + * // Pin from a spotter context (SDK: 1.45.2+) + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Pin, { + * vizId: latestSpotterVizId, + * newVizName: "AI-generated insights", + * liveboardId: "456" + * }, ContextType.Spotter); + * ``` + * + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["Pin"] = "pin"; + /** + * Trigger the **Show Liveboard details** action + * on an embedded Liveboard. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.LiveboardInfo) + *``` + * @example + * ```js + * // Show liveboard info from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.LiveboardInfo, {}, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["LiveboardInfo"] = "pinboardInfo"; + /** + * Trigger the **Schedule** action on an embedded Liveboard. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Schedule) + * ``` + * @example + * ```js + * // Schedule from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Schedule, {}, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["Schedule"] = "subscription"; + /** + * Trigger the **Manage schedule** action on an embedded Liveboard + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ScheduleList) + * ``` + * @example + * ```js + * // Manage schedules from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.ScheduleList, {}, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["SchedulesList"] = "schedule-list"; + /** + * Trigger the **Export TML** action on an embedded Liveboard or + * Answer. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ExportTML) + * ``` + * @example + * ```js + * // Export TML from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.ExportTML, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Export TML from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.ExportTML, {}, ContextType.Search); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["ExportTML"] = "exportTSL"; + /** + * Trigger the **Edit TML** action on an embedded Liveboard or + * saved Answers in the full application embedding. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.EditTML) + * ``` + * @example + * ```js + * // Edit TML from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.EditTML, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Edit TML from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.EditTML, {}, ContextType.Search); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["EditTML"] = "editTSL"; + /** + * Trigger the **Update TML** action on an embedded Liveboard. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateTML) + * ``` + * @example + * ```js + * // Update TML from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.UpdateTML, {}, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["UpdateTML"] = "updateTSL"; + /** + * Trigger the **Download PDF** action on an embedded Liveboard, + * visualization or Answer. + * + * @param - `vizId` refers to the Answer ID in Spotter embed and is required in Spotter embed. + * + * **NOTE**: The **Download** > **PDF** action is available on + * visualizations and Answers if the data is in tabular format. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.DownloadAsPdf) + * ``` + * @example + * ```js + + * // You can use the Data event dispatched on each answer creation to get the vizId and use in DownloadAsPdf host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.DownloadAsPdf, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Download as PDF from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.DownloadAsPdf, {}, ContextType.Search); + * ``` + * @example + * ```js + * // Download as PDF from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.DownloadAsPdf, {}, ContextType.Liveboard); + * ``` + * + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["DownloadAsPdf"] = "downloadAsPdf"; + /** + * Trigger the **Download Liveboard as Continuous PDF** action on an + * embedded Liveboard. + * + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.DownloadLiveboardAsContinuousPDF) + * ``` + * + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + HostEvent["DownloadLiveboardAsContinuousPDF"] = "downloadLiveboardAsContinuousPDF"; + /** + * Trigger the **AI Highlights** action on an embedded Liveboard + * + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.AIHighlights) + * ``` + * @version SDK: 1.44.0 | ThoughtSpot: 10.15.0.cl + */ + HostEvent["AIHighlights"] = "AIHighlights"; + /** + * Trigger the **Make a copy** action on a Liveboard, + * visualization, or Answer page. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.MakeACopy) + * ``` + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.MakeACopy, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * @example + * ```js + * vizEmbed.trigger(HostEvent.MakeACopy) + * ``` + * @example + * ```js + * searchEmbed.trigger(HostEvent.MakeACopy) + * ``` + * @example + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in MakeACopy host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.MakeACopy, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Make a copy from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.MakeACopy, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Make a copy from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.MakeACopy, {}, ContextType.Search); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["MakeACopy"] = "makeACopy"; + /** + * Trigger the **Delete** action for a Liveboard. + * @example + * ```js + * appEmbed.trigger(HostEvent.Remove) + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Remove) + * ``` + * @version SDK: 1.37.0 | ThoughtSpot: 10.8.0.cl, 10.10.0.sw + */ + HostEvent["Remove"] = "delete"; + /** + * Trigger the **Explore** action on a visualization. + * @param - an object with `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Explore, {vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * @example + * ```js + * // Explore from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Explore, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["Explore"] = "explore"; + /** + * Trigger the **Create alert** action on a KPI chart + * in a Liveboard or saved Answer. + * @param - an object with `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.CreateMonitor, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }) + * ``` + * @example + * ```js + * searchEmbed.trigger(HostEvent.CreateMonitor) + * ``` + * @example + * ```js + * // Create monitor from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.CreateMonitor, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Create monitor from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.CreateMonitor, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["CreateMonitor"] = "createMonitor"; + /** + * Trigger the **Manage alerts** action on a KPI chart + * in a visualization or saved Answer. + * @param - an object with `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ManageMonitor, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }) + * ``` + * @example + * ```js + * searchEmbed.trigger(HostEvent.ManageMonitor) + * ``` + * @example + * ```js + * vizEmbed.trigger(HostEvent.ManageMonitor) + * ``` + * @example + * ```js + * // Manage monitor from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.ManageMonitor, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Manage monitor from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.ManageMonitor, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["ManageMonitor"] = "manageMonitor"; + /** + * Trigger the **Edit** action on a Liveboard or a visualization + * on a Liveboard. + * + * This event is not supported in visualization embed and search embed. + * @param - Object parameter. Includes the following keys: + * - `vizId`: To trigger the action for a specific visualization in Liveboard embed, + * pass in `vizId` as a key. In Spotter embed, `vizId` refers to the Answer ID and + * is **required**. + * + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Edit) + * ``` + * ```js + * liveboardEmbed.trigger(HostEvent.Edit, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * @example + * ```js + * spotterEmbed.trigger(HostEvent.Edit); + * ``` + * @example + * ```js + * // Using context parameter to edit liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Edit, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Edit from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Edit, {}, ContextType.Search); + * ``` + * * @example + * ```js + * // Edit from spotter context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Edit, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Spotter); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["Edit"] = "edit"; + /** + * Trigger the **Copy link** action on a Liveboard or visualization + * @param - object - to trigger the action for a + * specific visualization in Liveboard embed, pass in `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.CopyLink) + * ``` + * ```js + * liveboardEmbed.trigger(HostEvent.CopyLink, {vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.CopyLink) + * ``` + * @example + * ```js + * // Copy link from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.CopyLink, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Copy link from liveboard visualization context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.CopyLink, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Copy link from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.CopyLink, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Copy link from liveboard visualization context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.CopyLink, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["CopyLink"] = "embedDocument"; + /** + * Trigger the **Present** action on a Liveboard or visualization + * @param - object - to trigger the action for a specific visualization + * in Liveboard embed, pass in `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Present) + * ``` + * ```js + * liveboardEmbed.trigger(HostEvent.Present, {vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.Present) + * ``` + * @example + * ```js + * // Present from liveboard visualization context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Present, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Present from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Present, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Present from liveboard visualization context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Present, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Present from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Present, {}, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["Present"] = "present"; + /** + * Get TML for the current search. + * @example + * ```js + * searchEmbed.trigger(HostEvent.GetTML).then((tml) => { + * console.log( + * tml.answer.search_query // TML representation of the search query + * ); + * }) + * ``` + * @example + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in GetTML host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.GetTML, { + * vizId: latestSpotterVizId + * }).then((tml) => { + * console.log( + * tml.answer.search_query // TML representation of the search query + * ); + * }) + * ``` + * @example + * ```js + * // Get TML from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.GetTML, {}, ContextType.Search).then((tml) => { + * console.log(tml.answer.search_query); + * }); + * ``` + * @example + * ```js + * // Get TML from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.GetTML, {}, ContextType.Search).then((tml) => { + * console.log(tml.answer); + * }); + * ``` + * @version SDK: 1.18.0 | ThoughtSpot: 8.10.0.cl, 9.0.1.sw + * @important + */ + HostEvent["GetTML"] = "getTML"; + /** + * Trigger the **Show underlying data** action on a + * chart or table. + * + * @param - an object with vizId as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ShowUnderlyingData, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.ShowUnderlyingData) + * ``` + * ```js + * searchEmbed.trigger(HostEvent.ShowUnderlyingData) + * ``` + * @example + * ```js + * // Show underlying data from liveboard visualization context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.ShowUnderlyingData, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Show underlying data from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.ShowUnderlyingData, {}, ContextType.Search); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["ShowUnderlyingData"] = "showUnderlyingData"; + /** + * Trigger the **Delete** action for a visualization + * in an embedded Liveboard, or a chart or table + * generated from Search. + * @param - Liveboard embed takes an object with `vizId` as a key. + * Can be left empty if embedding Search or visualization. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Delete, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * searchEmbed.trigger(HostEvent.Delete) + * ``` + * @example + * ```js + * // Delete from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Delete, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Delete from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Delete, {}, ContextType.Search); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["Delete"] = "onDeleteAnswer"; + /** + * Trigger the **SpotIQ analyze** action on a + * chart or table. + * @param - Liveboard embed takes `vizId` as a + * key. Can be left undefined when embedding Search or + * visualization. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SpotIQAnalyze, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.SpotIQAnalyze) + * ``` + * ```js + * searchEmbed.trigger(HostEvent.SpotIQAnalyze) + * ``` + * @example + * ```js + * // SpotIQ analyze from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.SpotIQAnalyze, { vizId: '730496d6-6903-4601-937e-2c691821af3c' }, ContextType.Search); + * ``` + * @example + * ```js + * // SpotIQ analyze from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SpotIQAnalyze, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["SpotIQAnalyze"] = "spotIQAnalyze"; + /** + * Trigger the **Download** action on charts in + * the embedded view. + * Use {@link HostEvent.DownloadAsPng} instead. + * + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + * + * @deprecated from SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl ,9.4.1.sw + * @param - `vizId` refers to the Visualization ID in Spotter embed and is required in Spotter embed. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Download, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * embed.trigger(HostEvent.Download) + * ``` + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in Download host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.Download, { vizId: latestSpotterVizId }); + * ``` + */ + HostEvent["Download"] = "downloadAsPng"; + /** + * Trigger the **Download** > **PNG** action on + * charts in the embedded view. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.DownloadAsPng, + * {vizId:'730496d6-6903-4601-937e-2c691821af3c'}) + * + * vizEmbed.trigger(HostEvent.DownloadAsPng) + * + * searchEmbed.trigger(HostEvent.DownloadAsPng) + * + * // You can use the Data event dispatched on each answer creation to get the vizId and use in DownloadAsPng host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.DownloadAsPng, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Download as PNG from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.DownloadAsPng, {}, ContextType.Search); + * ``` + * + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.4.1.sw + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + HostEvent["DownloadAsPng"] = "downloadAsPng"; + /** + * Trigger the **Download** > **CSV** action on tables in + * the embedded view. + * @param - `vizId` refers to the Visualization ID in Spotter embed and is required in Spotter embed. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.DownloadAsCsv, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.DownloadAsCsv) + * ``` + * ```js + * searchEmbed.trigger(HostEvent.DownloadAsCsv) + * ``` + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in DownloadAsCsv host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.DownloadAsCsv, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Download as CSV from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.DownloadAsCsv, {}, ContextType.Search); + * ``` + * @example + * ```js + * // Download as CSV from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.DownloadAsCsv, {}, ContextType.Search); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["DownloadAsCsv"] = "downloadAsCSV"; + /** + * Trigger the **Download** > **XLSX** action on tables + * in the embedded view. + * @param - `vizId` refers to the Visualization ID in Spotter embed and is required in Spotter embed. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.DownloadAsXlsx, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.DownloadAsXlsx) + * ``` + * ```js + * searchEmbed.trigger(HostEvent.DownloadAsXlsx) + * ``` + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in DownloadAsXlsx host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.DownloadAsXlsx, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Download as XLSX from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.DownloadAsXlsx, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Download as XLSX from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.DownloadAsXlsx, {}, ContextType.Search); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["DownloadAsXlsx"] = "downloadAsXLSX"; + /** + * Trigger the **Share** action on an embedded + * Liveboard or Answer. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Share) + * ``` + * ```js + * searchEmbed.trigger(HostEvent.Share) + * ``` + * @example + * ```js + * // Share from Liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Share, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Share from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Share, {}, ContextType.Search); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["Share"] = "share"; + /** + * Trigger the **Save** action on a Liveboard, Answer, or Spotter. + * Saves the changes. + * + * @param - `vizId` refers to the Spotter Visualization Id used in Spotter embed. + * It is required and can be retrieved from the data embed event. + * + * @example + * ```js + * // Save changes in a Liveboard + * liveboardEmbed.trigger(HostEvent.Save) + * ``` + * + * ```js + * // Save the current Answer in Search embed + * searchEmbed.trigger(HostEvent.Save) + * ``` + * + * ```js + * // Save a Visualization in Spotter (requires vizId) + * spotterEmbed.trigger(HostEvent.Save, { + * vizId: "730496d6-6903-4601-937e-2c691821af3c" + * }) + * ``` + * + * ```js + * // How to get the vizId in Spotter? + * + * // You can use the Data event dispatched on each answer creation to get the vizId. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.Save, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Save from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Save, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Save from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.Save, {}, ContextType.Search); + * ``` + * + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["Save"] = "save"; + /** + * Trigger the **Sync to Sheets** action on an embedded visualization or Answer + * Sends data from an Answer or Liveboard visualization to a Google sheet. + * @param - an object with `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SyncToSheets, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.SyncToSheets) + * ``` + * @example + * ```js + * // Sync to sheets from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.SyncToSheets, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Sync to sheets from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SyncToSheets, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["SyncToSheets"] = "sync-to-sheets"; + /** + * Trigger the **Sync to Other Apps** action on an embedded visualization or Answer + * Sends data from an Answer or Liveboard visualization to third-party apps such + * as Slack, Salesforce, Microsoft Teams, ServiceNow and so on. + * @param - an object with vizId as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SyncToOtherApps, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.SyncToOtherApps) + * ``` + * @example + * ```js + * // Sync to other apps from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.SyncToOtherApps, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Sync to other apps from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SyncToOtherApps, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["SyncToOtherApps"] = "sync-to-other-apps"; + /** + * Trigger the **Manage pipelines** action on an embedded + * visualization or Answer. + * Allows users to manage ThoughtSpot Sync pipelines. + * @param - an object with `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ManagePipelines, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.ManagePipelines) + * ``` + * @example + * ```js + * // Manage pipelines from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.ManagePipelines, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Manage pipelines from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.ManagePipelines, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["ManagePipelines"] = "manage-pipeline"; + /** + * Reset search operation on the Search or Answer page. + * @example + * ```js + * searchEmbed.trigger(HostEvent.ResetSearch) + * ``` + * ```js + * appEmbed.trigger(HostEvent.ResetSearch) + * ``` + * @example + * ```js + * // Reset search from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.ResetSearch, {}, ContextType.Search); + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.0.1.sw + */ + HostEvent["ResetSearch"] = "resetSearch"; + /** + * Get details of filters applied on the Liveboard. + * Returns arrays containing Liveboard filter and runtime filter elements. + * @example + * ```js + * const data = await liveboardEmbed.trigger(HostEvent.GetFilters); + * console.log('data', data); + * ``` + * @example + * ```js + * // Get filters from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * const data = await liveboardEmbed.trigger(HostEvent.GetFilters, {}, ContextType.Liveboard); + * console.log('filters', data); + * ``` + * @version SDK: 1.23.0 | ThoughtSpot: 9.4.0.cl + */ + HostEvent["GetFilters"] = "getFilters"; + /** + * Update one or several filters applied on a Liveboard. + * @param - Includes the following keys: + * - `filter`: A single filter object containing column name, filter operator, and + * values. + * - `filters`: Multiple filter objects with column name, filter operator, + * and values for each. + * + * Each filter object must include the following attributes: + * + * `column` - Name of the column to filter on. + * + * `oper` - Filter operator, for example, EQ, IN, CONTAINS. + * For information about the supported filter operators, + * see link:https://developers.thoughtspot.com/docs/runtime-filters#rtOperator[Developer Documentation]. + * + * `values` - An array of one or several values. The value definition on the + * data type you choose to filter on. For a complete list of supported data types, + * see + * link:https://developers.thoughtspot.com/docs/runtime-filters#_supported_data_types[Supported + * data types]. + * + * `type` - To update filters for date time, specify the date format type. + * For more information and examples, see link:https://developers.thoughtspot.com/docs/embed-liveboard#_date_filters[Date filters]. + * @example + * ```js + * + * liveboardEmbed.trigger(HostEvent.UpdateFilters, { + * filter: { + * column: "item type", + * oper: "IN", + * values: ["bags","shirts"] + * } + * }); + * ``` + * @example + * ```js + * + * liveboardEmbed.trigger(HostEvent.UpdateFilters, { + * filter: { + * column: "date", + * oper: "EQ", + * values: ["JULY","2023"], + * type: "MONTH_YEAR" + * } + * }); + * ``` + * @example + * + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateFilters, { + * filters: [{ + * column: "Item Type", + * oper: 'IN', + * values: ["bags","shirts"] + * }, + * { + * column: "Region", + * oper: 'IN', + * values: ["West","Midwest"] + * }, + * { + * column: "Date", + * oper: 'EQ', + * values: ["2023-07-31"], + * type: "EXACT_DATE" + * }] + * }); + * ``` + * If there are multiple columns with the same name, consider + * using `WORKSHEET_NAME::COLUMN_NAME` format. + * + * @example + * + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateFilters, { + * filters: [{ + * column: "(Sample) Retail - Apparel::city", + * oper: 'IN', + * values: ["atlanta"] + * }, + * { + * column: "(Sample) Retail - Apparel::Region", + * oper: 'IN', + * values: ["West","Midwest"] + * }] + * }); + * ``` + * @example + * ```js + * // Update filters from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.UpdateFilters, { + * filter: { + * column: "item type", + * oper: "IN", + * values: ["shoes", "boots"] + * } + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.23.0 | ThoughtSpot: 9.4.0.cl + */ + HostEvent["UpdateFilters"] = "updateFilters"; + /** + * Get tab details for the current Liveboard. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.GetTabs).then((tabDetails) => { + * console.log( + * tabDetails // TabDetails of current Liveboard + * ); + * }) + * ``` + * @example + * ```js + * // Get tabs from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.GetTabs, {}, ContextType.Liveboard).then((tabDetails) => { + * console.log('tabs', tabDetails); + * }); + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl + */ + HostEvent["GetTabs"] = "getTabs"; + /** + * Set the visible tabs on a Liveboard. + * @param - an array of ids of tabs to show, the IDs not passed + * will be hidden. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SetVisibleTabs, [ + * '430496d6-6903-4601-937e-2c691821af3c', + * 'f547ec54-2a37-4516-a222-2b06719af726']) + * ``` + * @example + * ```js + * // Set visible tabs from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SetVisibleTabs, [ + * '430496d6-6903-4601-937e-2c691821af3c', + * 'f547ec54-2a37-4516-a222-2b06719af726' + * ], ContextType.Liveboard); + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + */ + HostEvent["SetVisibleTabs"] = "SetPinboardVisibleTabs"; + /** + * Set the hidden tabs on a Liveboard. + * @param - an array of the IDs of the tabs to hide. + * The IDs not passed will be shown. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SetHiddenTabs, [ + * '630496d6-6903-4601-937e-2c691821af3c', + * 'i547ec54-2a37-4516-a222-2b06719af726']) + * ``` + * @example + * ```js + * // Set hidden tabs from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SetHiddenTabs, [ + * '630496d6-6903-4601-937e-2c691821af3c', + * 'i547ec54-2a37-4516-a222-2b06719af726' + * ], ContextType.Liveboard); + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + */ + HostEvent["SetHiddenTabs"] = "SetPinboardHiddenTabs"; + /** + * Get the Answer session for a Search or + * Liveboard visualization. + * + * Note: This event is not typically used directly. Instead, use the + * `getAnswerService()` method on the embed instance to get an AnswerService + * object that provides a more convenient interface for working with answers. + * + * @example + * ```js + * // Preferred way to get an AnswerService + * const service = await embed.getAnswerService(); + * + * // Alternative direct usage (not recommended) + * const {session} = await embed.trigger( + * HostEvent.GetAnswerSession, { + * vizId: '123', // For Liveboard Visualization. + * }) + * ``` + * @example + * ```js + * // Preferred way to get an AnswerService + * const service = await embed.getAnswerService(); + * + * // Alternative direct usage (not recommended) + * const {session} = await embed.trigger( HostEvent.GetAnswerSession ) + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.10.0.cl, 10.1.0.sw + */ + HostEvent["GetAnswerSession"] = "getAnswerSession"; + /** + * Trigger the *Ask Sage* action for visualizations + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.AskSage, + * {vizId:'730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * @version SDK: 1.29.0 | ThoughtSpot Cloud: 9.12.0.cl + */ + HostEvent["AskSage"] = "AskSage"; + /** + * Trigger cross filter update action on a Liveboard. + * + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateCrossFilter, { + * vizId: 'b535c760-8bbe-4e6f-bb26-af56b4129a1e', + * conditions: [ + * { columnName: 'Category', values: ['mfgr#12','mfgr#14'] }, + * { columnName: 'color', values: ['mint','hot'] }, + * ], + * }); + * ``` + * @version SDK: 1.29.0 | ThoughtSpot Cloud: 10.0.0.cl, 10.1.0.sw + */ + HostEvent["UpdateCrossFilter"] = "UpdateCrossFilter"; + /** + * Trigger reset action for a personalized Liveboard view. + * This event is deprecated. Use {@link HostEvent.ResetLiveboardPersonalizedView} instead. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ResetLiveboardPersonalisedView); + * ``` + * @version SDK: 1.29.0 | ThoughtSpot Cloud: 10.1.0.cl, 10.1.0.sw + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + HostEvent["ResetLiveboardPersonalisedView"] = "ResetLiveboardPersonalisedView"; + /** + * Trigger reset action for a personalized Liveboard view. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ResetLiveboardPersonalizedView); + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + HostEvent["ResetLiveboardPersonalizedView"] = "ResetLiveboardPersonalisedView"; + /** + * Triggers an action to update Parameter values on embedded + * Answers, Liveboard, and Spotter answer in Edit mode. + * @param - Includes the following keys for each item: + * - `name`: Name of the parameter. + * - `value`: The value to set for the parameter. + * - `isVisibleToUser`: Optional. To control the visibility of the parameter chip. + * + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateParameters, [{ + * name: "Integer Range Param", + * value: 10, + * isVisibleToUser: false + * }]) + * ``` + * @example + * ```js + * // Update parameters from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.UpdateParameters, [{ + * name: "Region Param", + * value: "West", + * isVisibleToUser: true + * }], ContextType.Liveboard); + * ``` + * @version SDK: 1.29.0 | ThoughtSpot: 10.1.0.cl, 10.1.0.sw + */ + HostEvent["UpdateParameters"] = "UpdateParameters"; + /** + * Triggers GetParameters to fetch the runtime Parameters. + * @param - `vizId` refers to the Answer ID in Spotter embed and is required in Spotter embed. + * ```js + * liveboardEmbed.trigger(HostEvent.GetParameters).then((parameter) => { + * console.log('parameters', parameter); + * }); + * ``` + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in GetParameters host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.GetParameters, { vizId: latestSpotterVizId }); + *``` + * @example + * ```js + * // Get parameters from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.GetParameters, {}, + * ContextType.Liveboard).then((parameters) => { + * console.log('parameters', parameters); + * }); + * ``` + * @version SDK: 1.29.0 | ThoughtSpot: 10.1.0.cl, 10.1.0.sw + */ + HostEvent["GetParameters"] = "GetParameters"; + /** + * Triggers an event to update a personalized view of a Liveboard. + * This event is deprecated. Use {@link HostEvent.UpdatePersonalizedView} instead. + * ```js + * liveboardEmbed.trigger(HostEvent.UpdatePersonalisedView, {viewId: '1234'}) + * ``` + * @example + * ```js + * // Update personalized view from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.UpdatePersonalisedView, { + * viewId: '1234' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.36.0 | ThoughtSpot: 10.6.0.cl + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + HostEvent["UpdatePersonalisedView"] = "UpdatePersonalisedView"; + /** + * Triggers an event to update a personalized view of a Liveboard. + * ```js + * liveboardEmbed.trigger(HostEvent.UpdatePersonalisedView, {viewId: '1234'}) + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + HostEvent["UpdatePersonalizedView"] = "UpdatePersonalisedView"; + /** + * Triggers selection of a specific Personalized View on a + * Liveboard without reloading the embed. Pass either a + * `viewId` (GUID) or `viewName`. If both are provided, `viewId` takes precedence. + * If neither is provided, the Liveboard resets to the original/default view. + * When a `viewName` is provided and multiple views share + * the same name, the first match is selected. + * @example + * ```js + * liveboardEmbed.trigger( + * HostEvent.SelectPersonalizedView, + * { viewId: '2a021a12-1aed-425d-984b-141ee916ce72' }, + * ) + * ``` + * @example + * ```js + * // Select by name + * liveboardEmbed.trigger( + * HostEvent.SelectPersonalizedView, + * { viewName: 'Dr Smith Cardiology' }, + * ) + * ``` + * @example + * ```js + * // Reset to default view + * liveboardEmbed.trigger( + * HostEvent.SelectPersonalizedView, + * {}, + * ) + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + HostEvent["SelectPersonalizedView"] = "SelectPersonalisedView"; + /** + * @hidden + * Notify when info call is completed successfully + * ```js + * liveboardEmbed.trigger(HostEvent.InfoSuccess, data); + * ``` + * @version SDK: 1.36.0 | ThoughtSpot: 10.6.0.cl + */ + HostEvent["InfoSuccess"] = "InfoSuccess"; + /** + * Trigger the save action for an Answer. + * To programmatically save an answer without opening the + * *Describe your Answer* modal, define the `name` and `description` + * properties. + * If no parameters are specified, the save action is + * triggered with a modal to prompt users to + * add a name and description for the Answer. + * @param - Includes the following keys: + * - `vizId`: Refers to the Answer ID in Spotter embed and is **required** in Spotter + * embed. + * - `name`: Optional. Name string for the Answer. + * - `description`: Optional. Description text for the Answer. + * @example + * ```js + * const saveAnswerResponse = await searchEmbed.trigger(HostEvent.SaveAnswer, { + * name: "Sales by states", + * description: "Total sales by states in MidWest" + * }); + * ``` + * @example + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in SaveAnswer host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.SaveAnswer, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Using context parameter to save answer from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * const saveAnswerResponse = await appEmbed.trigger(HostEvent.SaveAnswer, { + * name: "Regional sales analysis", + * description: "Sales breakdown by region" + * }, ContextType.Search); + * ``` + * @example + * ```js + * // Save answer from answer context (explore modal) + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * const saveAnswerResponse = await appEmbed.trigger(HostEvent.SaveAnswer, { + * name: "Modified analysis", + * description: "Updated from explore view" + * }, ContextType.Answer); + * ``` + * @example + * ```js + * // Save answer from spotter context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * const saveAnswerResponse = await appEmbed.trigger(HostEvent.SaveAnswer, { + * vizId: latestSpotterVizId, + * name: "AI insights", + * description: "Generated from Spotter" + * }, ContextType.Spotter); + * ``` + * @version SDK: 1.36.0 | ThoughtSpot: 10.6.0.cl + */ + HostEvent["SaveAnswer"] = "saveAnswer"; + /** + * EmbedApi + * @hidden + */ + HostEvent["UIPassthrough"] = "UiPassthrough"; + /** + * Triggers the table visualization re-render with the updated data. + * Includes the following properties: + * @param - `columnDataLite` - an array of object containing the + * data value modifications retrieved from the `EmbedEvent.TableVizRendered` + * payload.For example, { columnDataLite: []}`. + * + * @example + * ```js + * searchEmbed.on(EmbedEvent.TableVizRendered, (payload) => { + * console.log(payload); + * const columnDataLite = payload.data.data.columnDataLite; + * columnDataLite[0].dataValue[0]="new fob"; + * console.log('>>> new Data', columnDataLite); + * searchEmbed.trigger(HostEvent.TransformTableVizData, columnDataLite); + * }) + * ``` + * @version SDK: 1.37.0 | ThoughtSpot: 10.8.0.cl + */ + HostEvent["TransformTableVizData"] = "TransformTableVizData"; + /** + * Triggers a search operation with the search tokens specified in + * the search query string in spotter embed. + * @param - Includes the following keys: + * - `query`: Text string in Natural Language format. + * - `executeSearch`: Boolean to execute search and update search query. + * @example + * ```js + * spotterEmbed.trigger(HostEvent.SpotterSearch, { + * query: 'revenue per year', + * executeSearch: true, + * }) + * ``` + * @example + * ```js + * // Spotter search from spotter context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * spotterEmbed.trigger(HostEvent.SpotterSearch, { + * query: 'sales by region', + * executeSearch: true + * }, ContextType.Spotter); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HostEvent["SpotterSearch"] = "SpotterSearch"; + /** + * Edits the last prompt in spotter embed. + * @param - `query`: Text string + * @example + * ```js + * spotterEmbed.trigger(HostEvent.EditLastPrompt, "revenue per year"); + * ``` + * @example + * ```js + * // Edit last prompt from spotter context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * spotterEmbed.trigger(HostEvent.EditLastPrompt, "sales by region", ContextType.Spotter); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HostEvent["EditLastPrompt"] = "EditLastPrompt"; + /** + * Opens the data source preview modal in Spotter Embed. + * @example + * ```js + * spotterEmbed.trigger(HostEvent.PreviewSpotterData); + * ``` + * @example + * ```js + * // Preview spotter data from spotter context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * spotterEmbed.trigger(HostEvent.PreviewSpotterData, {}, ContextType.Spotter); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HostEvent["PreviewSpotterData"] = "PreviewSpotterData"; + /** + * Opens the Add to Coaching modal from a visualization in Spotter Embed. + * @param - `vizId ` refers to the Visualization ID in Spotter embed and is required. + * @example + * ```js + * spotterEmbed.trigger(HostEvent.AddToCoaching, { vizId: '730496d6-6903-4601-937e-2c691821af3c' }); + * + * ``` + * @version SDK: 1.45.0 | ThoughtSpot: 26.2.0.cl + */ + HostEvent["AddToCoaching"] = "addToCoaching"; + /** + * Opens the data model instructions modal in Spotter Embed. + * @example + * ```js + * spotterEmbed.trigger(HostEvent.DataModelInstructions); + * ``` + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + */ + HostEvent["DataModelInstructions"] = "DataModelInstructions"; + /** + * Resets the Spotter Embed Conversation. + * @example + * ```js + * spotterEmbed.trigger(HostEvent.ResetSpotterConversation); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HostEvent["ResetSpotterConversation"] = "ResetSpotterConversation"; + /** + * Deletes the last prompt in spotter embed. + * @example + * ```js + * spotterEmbed.trigger(HostEvent.DeleteLastPrompt); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HostEvent["DeleteLastPrompt"] = "DeleteLastPrompt"; + /** + * Toggle the visualization to chart or table view. + * @param - `vizId ` refers to the Visualization ID in Spotter embed and is required. + * @example + * ```js + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.AnswerChartSwitcher, { vizId: latestSpotterVizId }); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HostEvent["AnswerChartSwitcher"] = "answerChartSwitcher"; + /** + * @hidden + * Trigger exit from presentation mode when user exits fullscreen. + * This is automatically triggered by the SDK when fullscreen mode is exited. + * ```js + * liveboardEmbed.trigger(HostEvent.ExitPresentMode); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HostEvent["ExitPresentMode"] = "exitPresentMode"; + /** + * Triggers the full height lazy load data. + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.RequestVisibleEmbedCoordinates, (payload) => { + * console.log(payload); + * }); + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + * + * @hidden + */ + HostEvent["VisibleEmbedCoordinates"] = "visibleEmbedCoordinates"; + /** + * Trigger the *Spotter* action for visualizations present on the liveboard's vizzes. + * @param - `vizId` refers to the Visualization ID in Spotter embed and is required. + * @example + * ```js + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.AskSpotter, { vizId: latestSpotterVizId }); + * ``` + * @version SDK: 1.41.0 | ThoughtSpot: 10.12.0.cl + */ + HostEvent["AskSpotter"] = "AskSpotter"; + /** + * @hidden + * Triggers the update of the embed params. + * + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateEmbedParams, viewConfig); + * ``` + */ + HostEvent["UpdateEmbedParams"] = "updateEmbedParams"; + /** + * Triggered when the embed needs to be destroyed. This is used to clean up any embed-related resources internally. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.DestroyEmbed); + * ``` + * @version SDK: 1.41.0 | ThoughtSpot: 10.12.0.cl + */ + HostEvent["DestroyEmbed"] = "EmbedDestroyed"; + /** + * Triggers a new conversation in Spotter embed. + * + * This feature is available only when chat history is enabled on your ThoughtSpot + * instance. Contact your admin or ThoughtSpot Support to enable chat history on your + * instance. + * + * @example + * ```js + * spotterEmbed.trigger(HostEvent.StartNewSpotterConversation); + * ``` + * @version SDK: 1.45.0 | ThoughtSpot: 26.2.0.cl + */ + HostEvent["StartNewSpotterConversation"] = "StartNewSpotterConversation"; + /** + * @hidden + * Get the current context of the embedded page. + * + * @example + * ```js + * const context = await liveboardEmbed.trigger(HostEvent.GetPageContext); + * ``` + * @version SDK: 1.45.0 | ThoughtSpot: 26.2.0.cl + */ + HostEvent["GetPageContext"] = "GetPageContext"; + /** + * Trigger the **Send Test Email** action in the Liveboard schedule modal. + * Sends a test schedule email to self or all recipients. + * Requires `isSendNowLiveboardSchedulingEnabled` to be enabled. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SendTestScheduleEmail, { + * sendToSelf: true, + * }) + * ``` + * @example + * ```js + * // Send to all recipients + * liveboardEmbed.trigger(HostEvent.SendTestScheduleEmail, { + * sendToSelf: false, + * }) + * ``` + * @version SDK: 1.48.0 | ThoughtSpot Cloud: 26.5.0.cl + */ + HostEvent["SendTestScheduleEmail"] = "sendTestScheduleEmail"; + /** + * Sends a user message (prompt) to the SpotterViz panel programmatically. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @param query - the prompt text to send. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SpotterVizSendUserMessage, { + * query: 'Show me revenue by region', + * }); + * ``` + */ + HostEvent["SpotterVizSendUserMessage"] = "SpotterVizSendUserMessage"; + /** + * Initializes a new SpotterViz conversation. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.InitSpotterVizConversation); + * ``` + */ + HostEvent["InitSpotterVizConversation"] = "InitSpotterVizConversation"; + /** + * Clears browser cache and fetches new data for liveboard ChartViz Containers. + * Requires `enableLiveboardDataCache` to be enabled. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.RefreshLiveboardBrowserCache); + * ``` + * @version SDK: 1.49.0 | ThoughtSpot Cloud: 26.6.0.cl + */ + HostEvent["RefreshLiveboardBrowserCache"] = "refreshLiveboardBrowserCache"; + })(exports.HostEvent || (exports.HostEvent = {})); + /** + * The different visual modes that the data sources panel within + * search could appear in, such as hidden, collapsed, or expanded. + */ + var DataSourceVisualMode; + (function (DataSourceVisualMode) { + /** + * The data source panel is hidden. + */ + DataSourceVisualMode["Hidden"] = "hide"; + /** + * The data source panel is collapsed, but the user can manually expand it. + */ + DataSourceVisualMode["Collapsed"] = "collapse"; + /** + * The data source panel is expanded, but the user can manually collapse it. + */ + DataSourceVisualMode["Expanded"] = "expand"; + })(DataSourceVisualMode || (DataSourceVisualMode = {})); + /** + * The query params passed down to the embedded ThoughtSpot app + * containing configuration and/or visual information. + */ + var Param; + (function (Param) { + Param["Tsmcp"] = "tsmcp"; + Param["EmbedApp"] = "embedApp"; + Param["DataSources"] = "dataSources"; + Param["DataSourceMode"] = "dataSourceMode"; + Param["DisableActions"] = "disableAction"; + Param["DisableActionReason"] = "disableHint"; + Param["ForceTable"] = "forceTable"; + Param["preventLiveboardFilterRemoval"] = "preventPinboardFilterRemoval"; + Param["SearchQuery"] = "searchQuery"; + Param["HideActions"] = "hideAction"; + Param["HideObjects"] = "hideObjects"; + Param["HostAppUrl"] = "hostAppUrl"; + Param["EnableVizTransformations"] = "enableVizTransform"; + Param["EnableSearchAssist"] = "enableSearchAssist"; + Param["EnableConnectionNewExperience"] = "newConnectionsExperience"; + Param["EnablePendoHelp"] = "enablePendoHelp"; + Param["HideResult"] = "hideResult"; + Param["UseLastSelectedDataSource"] = "useLastSelectedSources"; + Param["Tag"] = "tag"; + Param["HideTagFilterChips"] = "hideTagFilterChips"; + Param["AutoLogin"] = "autoLogin"; + Param["searchTokenString"] = "searchTokenString"; + Param["executeSearch"] = "executeSearch"; + Param["fullHeight"] = "isFullHeightPinboard"; + Param["livedBoardEmbed"] = "isLiveboardEmbed"; + Param["searchEmbed"] = "isSearchEmbed"; + Param["vizEmbed"] = "isVizEmbed"; + Param["StringIDsUrl"] = "overrideStringIDsUrl"; + Param["Version"] = "sdkVersion"; + Param["ViewPortHeight"] = "viewPortHeight"; + Param["ViewPortWidth"] = "viewPortWidth"; + Param["VisibleActions"] = "visibleAction"; + Param["DisableLoginRedirect"] = "disableLoginRedirect"; + Param["visibleVizs"] = "pinboardVisibleVizs"; + Param["LiveboardV2Enabled"] = "isPinboardV2Enabled"; + Param["DataPanelV2Enabled"] = "enableDataPanelV2"; + Param["ShowAlerts"] = "showAlerts"; + Param["Locale"] = "locale"; + Param["CustomStyle"] = "customStyle"; + Param["ForceSAMLAutoRedirect"] = "forceSAMLAutoRedirect"; + // eslint-disable-next-line @typescript-eslint/no-shadow + Param["AuthType"] = "authType"; + Param["IconSpriteUrl"] = "iconSprite"; + Param["cookieless"] = "cookieless"; + // Deprecated: `isContextMenuEnabledOnLeftClick` + // Introduced: `contextMenuEnabledOnWhichClick` with values: 'left', + // 'right', or 'both'. This update only affects ThoughtSpot URL parameters + // and does not impact existing workflows or use cases. Added support for + // 'both' clicks in `contextMenuTrigger` configuration. + Param["ContextMenuTrigger"] = "contextMenuEnabledOnWhichClick"; + Param["LinkOverride"] = "linkOverride"; + Param["EnableLinkOverridesV2"] = "enableLinkOverridesV2"; + Param["blockNonEmbedFullAppAccess"] = "blockNonEmbedFullAppAccess"; + Param["ShowInsertToSlide"] = "insertInToSlide"; + Param["PrimaryNavHidden"] = "primaryNavHidden"; + Param["HideProfleAndHelp"] = "profileAndHelpInNavBarHidden"; + Param["NavigationVersion"] = "navigationVersion"; + Param["HideHamburger"] = "hideHamburger"; + Param["HideObjectSearch"] = "hideObjectSearch"; + Param["HideNotification"] = "hideNotification"; + Param["HideApplicationSwitcher"] = "applicationSwitcherHidden"; + Param["HideOrgSwitcher"] = "orgSwitcherHidden"; + Param["HideWorksheetSelector"] = "hideWorksheetSelector"; + Param["DisableWorksheetChange"] = "disableWorksheetChange"; + Param["HideSourceSelection"] = "hideSourceSelection"; + Param["DisableSourceSelection"] = "disableSourceSelection"; + Param["HideEurekaResults"] = "hideEurekaResults"; + Param["HideEurekaSuggestions"] = "hideEurekaSuggestions"; + Param["HideAutocompleteSuggestions"] = "hideAutocompleteSuggestions"; + Param["HideLiveboardHeader"] = "hideLiveboardHeader"; + Param["ShowLiveboardDescription"] = "showLiveboardDescription"; + Param["ShowLiveboardTitle"] = "showLiveboardTitle"; + Param["ShowMaskedFilterChip"] = "showMaskedFilterChip"; + Param["IsLiveboardMasterpiecesEnabled"] = "isLiveboardMasterpiecesEnabled"; + Param["EnableNewChartLibrary"] = "muzeChartPhase1EnabledGA"; + Param["HiddenTabs"] = "hideTabs"; + Param["VisibleTabs"] = "visibleTabs"; + Param["HideTabPanel"] = "hideTabPanel"; + Param["HideSampleQuestions"] = "hideSampleQuestions"; + Param["WorksheetId"] = "worksheet"; + Param["Query"] = "query"; + Param["HideHomepageLeftNav"] = "hideHomepageLeftNav"; + Param["ModularHomeExperienceEnabled"] = "modularHomeExperience"; + Param["HomepageVersion"] = "homepageVersion"; + Param["ListPageVersion"] = "listpageVersion"; + Param["PendoTrackingKey"] = "additionalPendoKey"; + Param["LiveboardHeaderSticky"] = "isLiveboardHeaderSticky"; + Param["IsProductTour"] = "isProductTour"; + Param["HideSearchBarTitle"] = "hideSearchBarTitle"; + Param["HideSageAnswerHeader"] = "hideSageAnswerHeader"; + Param["HideSearchBar"] = "hideSearchBar"; + Param["ClientLogLevel"] = "clientLogLevel"; + Param["ExposeTranslationIDs"] = "exposeTranslationIDs"; + Param["OverrideNativeConsole"] = "overrideConsoleLogs"; + Param["enableAskSage"] = "enableAskSage"; + Param["CollapseSearchBarInitially"] = "collapseSearchBarInitially"; + Param["DataPanelCustomGroupsAccordionInitialState"] = "dataPanelCustomGroupsAccordionInitialState"; + Param["EnableCustomColumnGroups"] = "enableCustomColumnGroups"; + Param["DateFormatLocale"] = "dateFormatLocale"; + Param["NumberFormatLocale"] = "numberFormatLocale"; + Param["CurrencyFormat"] = "currencyFormat"; + Param["Enable2ColumnLayout"] = "enable2ColumnLayout"; + Param["IsFullAppEmbed"] = "isFullAppEmbed"; + Param["IsOnBeforeGetVizDataInterceptEnabled"] = "isOnBeforeGetVizDataInterceptEnabled"; + Param["FocusSearchBarOnRender"] = "focusSearchBarOnRender"; + Param["DisableRedirectionLinksInNewTab"] = "disableRedirectionLinksInNewTab"; + Param["HomePageSearchBarMode"] = "homePageSearchBarMode"; + Param["ShowLiveboardVerifiedBadge"] = "showLiveboardVerifiedBadge"; + Param["ShowLiveboardReverifyBanner"] = "showLiveboardReverifyBanner"; + Param["LiveboardHeaderV2"] = "isLiveboardHeaderV2Enabled"; + Param["HideIrrelevantFiltersInTab"] = "hideIrrelevantFiltersAtTabLevel"; + Param["IsEnhancedFilterInteractivityEnabled"] = "isLiveboardPermissionV2Enabled"; + Param["SpotterEnabled"] = "isSpotterExperienceEnabled"; + Param["IsUnifiedSearchExperienceEnabled"] = "isUnifiedSearchExperienceEnabled"; + Param["OverrideOrgId"] = "orgId"; + Param["OauthPollingInterval"] = "oAuthPollingInterval"; + Param["IsForceRedirect"] = "isForceRedirect"; + Param["DataSourceId"] = "dataSourceId"; + Param["preAuthCache"] = "preAuthCache"; + Param["ShowSpotterLimitations"] = "showSpotterLimitations"; + Param["CoverAndFilterOptionInPDF"] = "arePdfCoverFilterPageCheckboxesEnabled"; + Param["PrimaryAction"] = "primaryAction"; + Param["isSpotterAgentEmbed"] = "isSpotterAgentEmbed"; + Param["IsLiveboardStylingAndGroupingEnabled"] = "isLiveboardStylingAndGroupingEnabled"; + Param["IsLazyLoadingForEmbedEnabled"] = "isLazyLoadingForEmbedEnabled"; + Param["RootMarginForLazyLoad"] = "rootMarginForLazyLoad"; + Param["isPNGInScheduledEmailsEnabled"] = "isPNGInScheduledEmailsEnabled"; + Param["IsWYSIWYGLiveboardPDFEnabled"] = "isWYSIWYGLiveboardPDFEnabled"; + Param["isLiveboardXLSXCSVDownloadEnabled"] = "isLiveboardXLSXCSVDownloadEnabled"; + Param["isGranularXLSXCSVSchedulesEnabled"] = "isGranularXLSXCSVSchedulesEnabled"; + Param["isSendNowLiveboardSchedulingEnabled"] = "isSendNowLiveboardSchedulingEnabled"; + Param["isCentralizedLiveboardFilterUXEnabled"] = "isCentralizedLiveboardFilterUXEnabled"; + Param["isLinkParametersEnabled"] = "isLinkParametersEnabled"; + Param["EnablePastConversationsSidebar"] = "enablePastConversationsSidebar"; + Param["UpdatedSpotterChatPrompt"] = "updatedSpotterChatPrompt"; + Param["EnableStopAnswerGenerationEmbed"] = "enableStopAnswerGenerationEmbed"; + Param["SpotterSidebarTitle"] = "spotterSidebarTitle"; + Param["SpotterSidebarDefaultExpanded"] = "spotterSidebarDefaultExpanded"; + Param["SpotterChatRenameLabel"] = "spotterChatRenameLabel"; + Param["SpotterChatDeleteLabel"] = "spotterChatDeleteLabel"; + Param["SpotterDeleteConversationModalTitle"] = "spotterDeleteConversationModalTitle"; + Param["SpotterPastConversationAlertMessage"] = "spotterPastConversationAlertMessage"; + Param["SpotterDocumentationUrl"] = "spotterDocumentationUrl"; + Param["SpotterBestPracticesLabel"] = "spotterBestPracticesLabel"; + Param["SpotterConversationsBatchSize"] = "spotterConversationsBatchSize"; + Param["SpotterNewChatButtonTitle"] = "spotterNewChatButtonTitle"; + Param["IsThisPeriodInDateFiltersEnabled"] = "isThisPeriodInDateFiltersEnabled"; + Param["HideToolResponseCardBranding"] = "hideToolResponseCardBranding"; + Param["ToolResponseCardBrandingLabel"] = "toolResponseCardBrandingLabel"; + Param["EnableHomepageAnnouncement"] = "enableHomepageAnnouncement"; + Param["EnableLiveboardDataCache"] = "enableLiveboardDataCache"; + Param["SpotterFileUploadEnabled"] = "spotterFileUploadEnabled"; + Param["SpotterFileUploadFileTypes"] = "spotterFileUploadFileTypes"; + })(Param || (Param = {})); + /** + * ThoughtSpot application pages include actions and menu commands + * for various user-initiated operations. These actions are represented + * as enumeration members in the SDK. To control actions in the embedded view: + * - disabledActions — the action is grayed out and still visible, but non-interactive (user can see but not click). + * - hiddenActions — the action is completely removed from the UI (user cannot see it at all). + * - visibleActions — allowlist, only these actions are shown; all others are hidden. + * + * Use disabledActions to disable (gray out) an action. + * Use hiddenActions to hide (fully remove) an action. + * Use visibleActions to show only specific actions. + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... //other embed view config + * visibleActions: [Action.Save, Action.Edit, Action.Present, Action.Explore], + * disabledActions: [Action.Download], + * //hiddenActions: [], // Set either this or visibleActions + * }) + * ``` + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... //other embed view config + * //visibleActions: [], + * disabledActions: [Action.Download], + * hiddenActions: [Action.Edit, Action.Explore], + * }) + * ``` + * See also link:https://developers.thoughtspot.com/docs/actions[Developer Documentation]. + */ + exports.Action = void 0; + (function (Action) { + /** + * The **Save** action on an Answer or Liveboard. + * Allows users to save the changes. + * @example + * ```js + * disabledActions: [Action.Save] + * ``` + */ + Action["Save"] = "save"; + /** + * @hidden + */ + Action["Update"] = "update"; + /** + * @hidden + */ + Action["SaveUntitled"] = "saveUntitled"; + /** + * The **Save as View** action on the Answer + * page. Saves an Answer as a View object in the full + * application embedding mode. + * @example + * ```js + * disabledActions: [Action.SaveAsView] + * ``` + */ + Action["SaveAsView"] = "saveAsView"; + /** + * The **Make a copy** action on a Liveboard or Answer + * page. Creates a copy of the Liveboard. + * In LiveboardEmbed, the **Make a copy** action is not available for + * visualizations in the embedded Liveboard view. + * In AppEmbed, the **Make a copy** action is available on both + * Liveboards and visualizations. + * @example + * ```js + * disabledActions: [Action.MakeACopy] + * ``` + */ + Action["MakeACopy"] = "makeACopy"; + /** + * The **Copy and Edit** action on a Liveboard. + * This action is now replaced with `Action.MakeACopy`. + * @example + * ```js + * disabledActions: [Action.EditACopy] + * ``` + */ + Action["EditACopy"] = "editACopy"; + /** + * The **Copy link** menu action on a Liveboard visualization. + * Copies the visualization URL + * @example + * ```js + * disabledActions: [Action.CopyLink] + * ``` + */ + Action["CopyLink"] = "embedDocument"; + /** + * @hidden + */ + Action["ResetLayout"] = "resetLayout"; + /** + * The **Schedule** menu action on a Liveboard. + * Allows scheduling a Liveboard job, for example, + * sending periodic notifications. + * @example + * ```js + * disabledActions: [Action.Schedule] + * ``` + */ + Action["Schedule"] = "subscription"; + /** + * The **Manage schedules** menu action on a Liveboard. + * Allows users to manage scheduled Liveboard jobs. + * @example + * ```js + * disabledActions: [Action.SchedulesList] + * ``` + */ + Action["SchedulesList"] = "schedule-list"; + /** + * The **Share** action on a Liveboard, Answer, or Model. + * Allows users to share an object with other users and groups. + * @example + * ```js + * disabledActions: [Action.Share] + * ``` + */ + Action["Share"] = "share"; + /** + * The **Add filter** action on a Liveboard page. + * Allows adding filters to visualizations on a Liveboard. + * @example + * ```js + * disabledActions: [Action.AddFilter] + * ``` + */ + Action["AddFilter"] = "addFilter"; + /** + * The **Add Data Panel Objects** action on the data panel v2. + * Allows to show action menu to add different objects (such as + * formulas, Parameters) in data panel new experience. + * @example + * ```js + * disabledActions: [Action.AddDataPanelObjects] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl, 10.1.0.sw + */ + Action["AddDataPanelObjects"] = "addDataPanelObjects"; + /** + * The filter configuration options for a Liveboard. + * The configuration options are available when adding + * filters on a Liveboard. + * + * @example + * ```js + * disabledActions: [Action.ConfigureFilter] + * ``` + */ + Action["ConfigureFilter"] = "configureFilter"; + /** + * The **Collapse data sources** icon on the Search page. + * Collapses the panel showing data sources. + * + * @example + * ```js + * disabledActions: [Action.CollapseDataPanel] + * ``` + * @version SDK: 1.1.0 | ThoughtSpot Cloud: ts7.may.cl, 8.4.1.sw + */ + Action["CollapseDataSources"] = "collapseDataSources"; + /** + * The **Collapse data panel** icon on the Search page. + * Collapses the data panel view. + * + * @version SDK: 1.34.0 | ThoughtSpot Cloud: 10.3.0.cl + * + * @example + * ```js + * disabledActions: [Action.CollapseDataPanel] + * ``` + */ + Action["CollapseDataPanel"] = "collapseDataPanel"; + /** + * The **Choose sources** button on Search page. + * Allows selecting data sources for search queries. + * @example + * ```js + * disabledActions: [Action.ChooseDataSources] + * ``` + */ + Action["ChooseDataSources"] = "chooseDataSources"; + /** + * The **Create formula** action on a Search or Answer page. + * Allows adding formulas to an Answer. + * @example + * ```js + * disabledActions: [Action.AddFormula] + * ``` + */ + Action["AddFormula"] = "addFormula"; + /** + * The **Add parameter** action on a Liveboard or Answer. + * Allows adding Parameters to a Liveboard or Answer. + * @example + * ```js + * disabledActions: [Action.AddParameter] + * ``` + */ + Action["AddParameter"] = "addParameter"; + /** + * The **Add Column Set** action on a Answer. + * Allows adding column sets to a Answer. + * @example + * ```js + * disabledActions: [Action.AddColumnSet] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl, 10.1.0.sw + */ + Action["AddColumnSet"] = "addSimpleCohort"; + /** + * The **Add Query Set** action on a Answer. + * Allows adding query sets to a Answer. + * @example + * ```js + * disabledActions: [Action.AddQuerySet] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl, 10.1.0.sw + */ + Action["AddQuerySet"] = "addAdvancedCohort"; + /** + * @hidden + */ + Action["SearchOnTop"] = "searchOnTop"; + /** + * The **SpotIQ analyze** menu action on a visualization or + * Answer page. + * @example + * ```js + * disabledActions: [Action.SpotIQAnalyze] + * ``` + */ + Action["SpotIQAnalyze"] = "spotIQAnalyze"; + /** + * @hidden + */ + Action["ExplainInsight"] = "explainInsight"; + /** + * @hidden + */ + Action["SpotIQFollow"] = "spotIQFollow"; + /** + * The Share action for a Liveboard visualization. + */ + Action["ShareViz"] = "shareViz"; + /** + * @hidden + */ + Action["ReplaySearch"] = "replaySearch"; + /** + * The **Show underlying data** menu action on a + * visualization or Answer page. + * Displays detailed information and raw data + * for a given visualization. + * @example + * ```js + * disabledActions: [Action.ShowUnderlyingData] + * ``` + */ + Action["ShowUnderlyingData"] = "showUnderlyingData"; + /** + * The **Download** menu action on Liveboard + * visualizations and Answers. + * Allows downloading a visualization or Answer. + * @example + * ```js + * disabledActions: [Action.DownloadAsPng] + * ``` + */ + Action["Download"] = "download"; + /** + * The **Download** > **PNG** menu action for charts on a Liveboard + * or Answer page. + * Downloads a visualization or Answer as a PNG file. + * @example + * ```js + * disabledActions: [Action.DownloadAsPng] + * ``` + */ + Action["DownloadAsPng"] = "downloadAsPng"; + /** + * + *The **Download PDF** action that downloads a Liveboard, + * visualization, or Answer as a PDF file. + * + * **NOTE**: The **Download** > **PDF** option is available for + * tables in visualizations and Answers. + * @example + * ```js + * disabledActions: [Action.DownloadAsPdf] + * ``` + */ + Action["DownloadAsPdf"] = "downloadAsPdf"; + /** + * The **Download** > **CSV** menu action for tables on a Liveboard + * or Answer page. + * Downloads a visualization or Answer in the XLSX format. + * @example + * ```js + * disabledActions: [Action.DownloadAsCsv] + * ``` + */ + Action["DownloadAsCsv"] = "downloadAsCSV"; + /** + * The **Download** > **XLSX** menu action for tables on a Liveboard + * or Answer page. + * Downloads a visualization or Answer in the XLSX format. + * @example + * ```js + * disabledActions: [Action.DownloadAsXlsx] + * ``` + */ + Action["DownloadAsXlsx"] = "downloadAsXLSX"; + /** + * The **Download Liveboard** menu action on a Liveboard. + * Allows downloading the entire Liveboard. + * @example + * ```js + * disabledActions: [Action.DownloadLiveboard] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + Action["DownloadLiveboard"] = "downloadLiveboard"; + /** + * The **Download Liveboard as Continuous PDF** menu action on a Liveboard. + * Allows downloading the entire Liveboard as a continuous PDF. + * @example + * ```js + * disabledActions: [Action.DownloadLiveboardAsContinuousPDF] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + Action["DownloadLiveboardAsContinuousPDF"] = "downloadLiveboardAsContinuousPDF"; + /** + * The Download Liveboard as A4 PDF menu action on a Liveboard. + * Allows downloading the entire Liveboard as an A4 PDF. + * Requires {@link Action.DownloadLiveboard} as a parent action when + * {@link LiveboardViewConfig.isLiveboardXLSXCSVDownloadEnabled} or + * {@link LiveboardViewConfig.isContinuousLiveboardPDFEnabled} flags are enabled. + * Use this instead of {@link Action.DownloadAsPdf} when either flag is on. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * disabledActions: [Action.DownloadLiveboardAsA4Pdf] + * ``` + */ + Action["DownloadLiveboardAsA4Pdf"] = "downloadLiveboardAsA4Pdf"; + /** + * The **Download Liveboard as XLSX** menu action on a Liveboard. + * Allows downloading the entire Liveboard as an XLSX file. + * @example + * ```js + * disabledActions: [Action.DownloadLiveboardAsXlsx] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + Action["DownloadLiveboardAsXlsx"] = "downloadLiveboardAsXlsx"; + /** + * The **Download Liveboard as CSV** menu action on a Liveboard. + * Allows downloading the entire Liveboard as a CSV file. + * @example + * ```js + * disabledActions: [Action.DownloadLiveboardAsCsv] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + Action["DownloadLiveboardAsCsv"] = "downloadLiveboardAsCsv"; + /** + * @hidden + */ + Action["DownloadTrace"] = "downloadTrace"; + /** + * The **Export TML** menu action on a Liveboard, Answer, and + * the Data Workspace pages for data objects and connections. + * + * Allows exporting an object as a TML file. + * + * @example + * ```js + * disabledActions: [Action.ExportTML] + * ``` + */ + Action["ExportTML"] = "exportTSL"; + /** + * The **Import TML** menu action on the + * *Data Workspace* > *Utilities* page. + * Imports TML representation of ThoughtSpot objects. + * @example + * ```js + * disabledActions: [Action.ImportTML] + * ``` + */ + Action["ImportTML"] = "importTSL"; + /** + * The **Update TML** menu action for Liveboards and Answers. + * Updates TML representation of ThoughtSpot objects. + * @example + * ```js + * disabledActions: [Action.UpdateTML] + * ``` + */ + Action["UpdateTML"] = "updateTSL"; + /** + * The **Edit TML** menu action for Liveboards and Answers. + * Opens the TML editor. + * @example + * ```js + * disabledActions: [Action.EditTML] + * ``` + */ + Action["EditTML"] = "editTSL"; + /** + * The **Present** menu action for Liveboards and Answers. + * Allows presenting a Liveboard or visualization in + * slideshow mode. + * @example + * ```js + * disabledActions: [Action.Present] + * ``` + */ + Action["Present"] = "present"; + /** + * The visualization tile resize option. + * Also available via More `...` options menu on a visualization. + * Allows resizing visualization tiles and switching + * between different preset layout option. + * + * @example + * ```js + * disabledActions: [Action.ToggleSize] + * ``` + */ + Action["ToggleSize"] = "toggleSize"; + /** + * The *Edit* action on the Liveboard page and in the + * visualization menu. + * Opens a Liveboard or visualization in edit mode. + * @example + * ```js + * disabledActions: [Action.Edit] + * ``` + */ + Action["Edit"] = "edit"; + /** + * The text edit option for Liveboard and visualization titles. + * @example + * ```js + * disabledActions: [Action.EditTitle] + * ``` + */ + Action["EditTitle"] = "editTitle"; + /** + * The **Delete** action on a Liveboard, *Liveboards* and + * *Answers* list pages in full application embedding. + * + * @example + * ```js + * disabledActions: [Action.Remove] + * ``` + */ + Action["Remove"] = "delete"; + /** + * @hidden + */ + Action["Ungroup"] = "ungroup"; + /** + * @hidden + */ + Action["Describe"] = "describe"; + /** + * @hidden + */ + Action["Relate"] = "relate"; + /** + * @hidden + */ + Action["CustomizeHeadlines"] = "customizeHeadlines"; + /** + * @hidden + */ + Action["PinboardInfo"] = "pinboardInfo"; + /** + * The **Show Liveboard details** menu action on a Liveboard. + * Displays details such as the name, description, and + * author of the Liveboard, and timestamp of Liveboard creation + * and update. + * @example + * ```js + * disabledActions: [Action.LiveboardInfo] + * ``` + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + Action["LiveboardInfo"] = "pinboardInfo"; + /** + * @hidden + */ + Action["SendAnswerFeedback"] = "sendFeedback"; + /** + * @hidden + */ + Action["DownloadEmbraceQueries"] = "downloadEmbraceQueries"; + /** + * The **Pin** menu action on an Answer or + * Search results page. + * @example + * ```js + * disabledActions: [Action.Pin] + * ``` + */ + Action["Pin"] = "pin"; + /** + * @hidden + */ + Action["AnalysisInfo"] = "analysisInfo"; + /** + * The **Schedule** menu action on a Liveboard. + * Allows scheduling a Liveboard job. + * @example + * ```js + * disabledActions: [Action.Subscription] + * ``` + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + Action["Subscription"] = "subscription"; + /** + * The **Explore** action on Liveboard visualizations + * @example + * ```js + * disabledActions: [Action.Explore] + * ``` + */ + Action["Explore"] = "explore"; + /** + * The contextual menu action to include a specific data point + * when drilling down a table or chart on an Answer. + * + * @example + * ```js + * disabledActions: [Action.DrillInclude] + * ``` + */ + Action["DrillInclude"] = "context-menu-item-include"; + /** + * The contextual menu action to exclude a specific data point + * when drilling down a table or chart on an Answer. + * @example + * ```js + * disabledActions: [Action.DrillInclude] + * ``` + */ + Action["DrillExclude"] = "context-menu-item-exclude"; + /** + * The **Copy to clipboard** menu action on tables in an Answer + * or Liveboard. + * Copies the selected data point. + * @example + * ```js + * disabledActions: [Action.CopyToClipboard] + * ``` + */ + Action["CopyToClipboard"] = "context-menu-item-copy-to-clipboard"; + Action["CopyAndEdit"] = "context-menu-item-copy-and-edit"; + /** + * @hidden + */ + Action["DrillEdit"] = "context-menu-item-edit"; + Action["EditMeasure"] = "context-menu-item-edit-measure"; + Action["Separator"] = "context-menu-item-separator"; + /** + * The **Drill down** menu action on Answers and Liveboard + * visualizations. + * Allows drilling down to a specific data point on a chart or table. + * @example + * ```js + * disabledActions: [Action.DrillDown] + * ``` + */ + Action["DrillDown"] = "DRILL"; + /** + * The request access action on Liveboards. + * Allows users with view permissions to request edit access to a Liveboard. + * @example + * ```js + * disabledActions: [Action.RequestAccess] + * ``` + */ + Action["RequestAccess"] = "requestAccess"; + /** + * Controls the display and availability of the **Query visualizer** and + * **Query SQL** buttons in the Query details panel on the Answer page. + * + * **Query visualizer** - Displays the tables and filters used in the search query. + * **Query SQL** - Displays the SQL statements used to retrieve data for the query. + * + * Note: This action ID only affects the visibility of the buttons within the + * Query details panel. It does not control the visibility + * of the query details icon on the Answer page. + * @example + * ```js + * disabledActions: [Action.QueryDetailsButtons] + * ``` + */ + Action["QueryDetailsButtons"] = "queryDetailsButtons"; + /** + * The **Delete** action for Answers in the full application + * embedding mode. + * @example + * ```js + * disabledActions: [Action.AnswerDelete] + * ``` + * @version SDK: 1.9.0 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + */ + Action["AnswerDelete"] = "onDeleteAnswer"; + /** + * The chart switcher icon on Answer page and + * visualizations in edit mode. + * Allows switching to the table or chart mode + * when editing a visualization. + * @example + * ```js + * disabledActions: [Action.AnswerChartSwitcher] + * ``` + * @version SDK: 1.9.0 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + */ + Action["AnswerChartSwitcher"] = "answerChartSwitcher"; + /** + * The Favorites icon (*) for Answers, + * Liveboard, and data objects like Model, + * Tables and Views. + * Allows adding an object to the user's favorites list. + * @example + * ```js + * disabledActions: [Action.AddToFavorites] + * ``` + * @version SDK: 1.9.0 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + */ + Action["AddToFavorites"] = "addToFavorites"; + /** + * The edit icon on Liveboards (Classic experience). + * @example + * ```js + * disabledActions: [Action.EditDetails] + * ``` + * @version SDK: 1.9.0 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + */ + Action["EditDetails"] = "editDetails"; + /** + * The *Create alert* action for KPI charts. + * Allows users to schedule threshold-based alerts + * for KPI charts. + * @example + * ```js + * disabledActions: [Action.CreateMonitor] + * ``` + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + */ + Action["CreateMonitor"] = "createMonitor"; + /** + * @version SDK: 1.11.1 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @deprecated This action is deprecated. It was used for reporting errors. + * @example + * ```js + * disabledActions: [Action.ReportError] + * ``` + */ + Action["ReportError"] = "reportError"; + /** + * The **Sync to sheets** action on Answers and Liveboard visualizations. + * Allows sending data to a Google Sheet. + * @example + * ```js + * disabledActions: [Action.SyncToSheets] + * ``` + * @version SDK: 1.18.0 | ThoughtSpot: 8.10.0.cl, 9.0.1.sw + */ + Action["SyncToSheets"] = "sync-to-sheets"; + /** + * The **Sync to other apps** action on Answers and Liveboard visualizations. + * Allows sending data to third-party apps like Slack, Salesforce, + * Microsoft Teams, and so on. + * @example + * ```js + * disabledActions: [Action.SyncToOtherApps] + * ``` + * @version SDK: 1.18.0 | ThoughtSpot: 8.10.0.cl, 9.0.1.sw + */ + Action["SyncToOtherApps"] = "sync-to-other-apps"; + /** + * The **Manage pipelines** action on Answers and Liveboard visualizations. + * Allows users to manage data sync pipelines to third-party apps. + * @example + * ```js + * disabledActions: [Action.ManagePipelines] + * ``` + * @version SDK: 1.18.0 | ThoughtSpot: 8.10.0.cl, 9.0.1.sw + */ + Action["ManagePipelines"] = "manage-pipeline"; + /** + * The **Filter** action on Liveboard visualizations. + * Allows users to apply cross-filters on a Liveboard. + * @example + * ```js + * disabledActions: [Action.CrossFilter] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.8.0.sw + */ + Action["CrossFilter"] = "context-menu-item-cross-filter"; + /** + * The **Sync to Slack** action on Liveboard visualizations. + * Allows sending data to third-party apps like Slack. + * @example + * ```js + * disabledActions: [Action.SyncToSlack] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot Cloud: 10.1.0.cl + */ + Action["SyncToSlack"] = "syncToSlack"; + /** + * The **Sync to Teams** action on Liveboard visualizations. + * Allows sending data to third-party apps like Microsoft Teams. + * @example + * ```js + * disabledActions: [Action.SyncToTeams] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot Cloud: 10.1.0.cl + */ + Action["SyncToTeams"] = "syncToTeams"; + /** + * The **Remove** action that appears when cross filters are applied + * on a Liveboard. + * Removes filters applied to a visualization. + * @example + * ```js + * disabledActions: [Action.RemoveCrossFilter] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["RemoveCrossFilter"] = "context-menu-item-remove-cross-filter"; + /** + * The **Aggregate** option in the chart axis or the + * table column customization menu. + * Provides aggregation options to analyze the data on a chart or table. + * @example + * ```js + * disabledActions: [Action.AxisMenuAggregate] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuAggregate"] = "axisMenuAggregate"; + /** + * The **Time bucket** option in the chart axis or table column + * customization menu. + * Allows defining time metric for date comparison. + * @example + * ```js + * disabledActions: [Action.AxisMenuTimeBucket] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuTimeBucket"] = "axisMenuTimeBucket"; + /** + * The **Filter** action in the chart axis or table column + * customization menu. + * Allows adding, editing, or removing filters. + * + * @example + * ```js + * disabledActions: [Action.AxisMenuFilter] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuFilter"] = "axisMenuFilter"; + /** + * The **Conditional formatting** action on chart or table. + * Allows adding rules for conditional formatting of data + * points on a chart or table. + * @example + * ```js + * disabledActions: [Action.AxisMenuConditionalFormat] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuConditionalFormat"] = "axisMenuConditionalFormat"; + /** + * The **Sort** menu action on a table or chart axis + * Sorts data in ascending or descending order. + * Allows adding, editing, or removing filters. + * @example + * ```js + * disabledActions: [Action.AxisMenuConditionalFormat] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuSort"] = "axisMenuSort"; + /** + * The **Group** option in the chart axis or table column + * customization menu. + * Allows grouping data points if the axes use the same + * unit of measurement and a similar scale. + * @example + * ```js + * disabledActions: [Action.AxisMenuGroup] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuGroup"] = "axisMenuGroup"; + /** + * The **Position** option in the axis customization menu. + * Allows changing the position of the axis to the + * left or right side of the chart. + * @example + * ```js + * disabledActions: [Action.AxisMenuPosition] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuPosition"] = "axisMenuPosition"; + /** + * The **Rename** option in the chart axis or table column customization menu. + * Renames the axis label on a chart or the column header on a table. + * @example + * ```js + * disabledActions: [Action.AxisMenuRename] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuRename"] = "axisMenuRename"; + /** + * The **Edit** action in the axis customization menu. + * Allows editing the axis name, position, minimum and maximum values, + * and format of a column. + * @example + * ```js + * disabledActions: [Action.AxisMenuEdit] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuEdit"] = "axisMenuEdit"; + /** + * The **Number format** action to customize the format of + * the data labels on a chart or table. + * @example + * ```js + * disabledActions: [Action.AxisMenuNumberFormat] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuNumberFormat"] = "axisMenuNumberFormat"; + /** + * The **Text wrapping** action on a table. + * Wraps or clips column text on a table. + * @example + * ```js + * disabledActions: [Action.AxisMenuTextWrapping] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuTextWrapping"] = "axisMenuTextWrapping"; + /** + * The **Remove** action in the chart axis or table column + * customization menu. + * Removes the data labels from a chart or the column of a + * table visualization. + * @example + * ```js + * disabledActions: [Action.AxisMenuRemove] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuRemove"] = "axisMenuRemove"; + /** + * The **Compare with** action in the chart axis customization menu. + * Allows comparing data across dimensions or measures. + * @example + * ```js + * disabledActions: [Action.AxisMenuCompare] + * ``` + * @version SDK: 1.50.0 | ThoughtSpot: 26.7.0.cl + */ + Action["AxisMenuCompare"] = "axisMenuCompare"; + /** + * The **Merge with** action in the chart axis customization menu. + * Allows merging data across dimensions or measures. + * @example + * ```js + * disabledActions: [Action.AxisMenuMerge] + * ``` + * @version SDK: 1.50.0 | ThoughtSpot: 26.7.0.cl + */ + Action["AxisMenuMerge"] = "axisMenuMerge"; + /** + * @hidden + */ + Action["InsertInToSlide"] = "insertInToSlide"; + /** + * The **Rename** menu action on Liveboards and visualizations. + * Allows renaming a Liveboard or visualization. + * @example + * ```js + * disabledActions: [Action.RenameModalTitleDescription] + * ``` + * @version SDK: 1.23.0 | ThoughtSpot: 9.4.0.cl, 9.8.0.sw + */ + Action["RenameModalTitleDescription"] = "renameModalTitleDescription"; + /** + * The *Request verification* action on a Liveboard. + * Initiates a request for Liveboard verification. + * @example + * ```js + * disabledActions: [Action.RequestVerification] + * ``` + * @version SDK: 1.25.0 | ThoughtSpot: 9.6.0.cl, 10.1.0.sw + */ + Action["RequestVerification"] = "requestVerification"; + /** + * + * Allows users to mark a Liveboard as verified. + * @example + * ```js + * disabledActions: [Action.MarkAsVerified] + * ``` + * @version SDK: 1.25.0 | ThoughtSpot: 9.6.0.cl, 10.1.0.sw + */ + Action["MarkAsVerified"] = "markAsVerified"; + /** + * The **Add Tab** action on a Liveboard. + * Allows adding a new tab to a Liveboard view. + * @example + * ```js + * disabledActions: [Action.AddTab] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + */ + Action["AddTab"] = "addTab"; + /** + * + * Initiates contextual change analysis on KPI charts. + * @example + * ```js + * disabledActions: [Action.EnableContextualChangeAnalysis] + * ``` + * @version SDK: 1.25.0 | ThoughtSpot Cloud: 9.6.0.cl + */ + Action["EnableContextualChangeAnalysis"] = "enableContextualChangeAnalysis"; + /** + * Action ID to hide or disable Iterative Change Analysis option + * in the contextual change analysis Insight charts context menu. + * + * @example + * ```js + * disabledActions: [Action.EnableIterativeChangeAnalysis] + * ``` + * @version SDK: 1.41.0 | ThoughtSpot Cloud: 9.12.0.cl + */ + Action["EnableIterativeChangeAnalysis"] = "enableIterativeChangeAnalysis"; + /** + * Action ID to hide or disable Natural Language Search query. + * + * @example + * ```js + * disabledActions: [Action.ShowSageQuery] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot Cloud: 9.7.0.cl + */ + Action["ShowSageQuery"] = "showSageQuery"; + /** + * + * Action ID to hide or disable the edit option for the + * results generated from the + * Natural Language Search query. + * + * @example + * ```js + * disabledActions: [Action.EditSageAnswer] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot Cloud: 9.7.0.cl + */ + Action["EditSageAnswer"] = "editSageAnswer"; + /** + * The feedback widget for AI-generated Answers. + * Allows users to send feedback on the Answers generated + * from a Natural Language Search query. + * + * @example + * ```js + * disabledActions: [Action.SageAnswerFeedback] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl + */ + Action["SageAnswerFeedback"] = "sageAnswerFeedback"; + /** + * + * @example + * ```js + * disabledActions: [Action.ModifySageAnswer] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl + */ + Action["ModifySageAnswer"] = "modifySageAnswer"; + /** + * The **Move to Tab** menu action on visualizations in Liveboard edit mode. + * Allows moving a visualization to a different tab. + * @example + * ```js + * disabledActions: [Action.MoveToTab] + * ``` + */ + Action["MoveToTab"] = "onContainerMove"; + /** + * The **Manage Alerts** menu action on KPI visualizations. + * Allows creating, viewing, and editing monitor + * alerts for a KPI chart. + * + * @example + * ```js + * disabledActions: [Action.ManageMonitor] + * ``` + */ + Action["ManageMonitor"] = "manageMonitor"; + /** + * The Liveboard Personalised Views dropdown. + * Allows navigating to a personalized Liveboard View. + * This action is deprecated. Use {@link Action.PersonalizedViewsDropdown} instead. + * @example + * ```js + * disabledActions: [Action.PersonalisedViewsDropdown] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 10.1.0.sw + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + Action["PersonalisedViewsDropdown"] = "personalisedViewsDropdown"; + /** + * The Liveboard Personalized Views dropdown. + * Allows navigating to a personalized Liveboard View. + * @example + * ```js + * disabledActions: [Action.PersonalizedViewsDropdown] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + Action["PersonalizedViewsDropdown"] = "personalisedViewsDropdown"; + /** + * Action ID for show or hide the user details on a + * Liveboard (Recently visited / social proof) + * @example + * ```js + * disabledActions: [Action.LiveboardUsers] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 10.1.0.sw + */ + Action["LiveboardUsers"] = "liveboardUsers"; + /** + * Action ID for the Parent TML action + * The parent action **TML** must be included to access TML-related options + * within the cascading menu (specific to the Answer page) + * @example + * ```js + * // to include specific TML actions + * visibleActions: [Action.TML, Action.ExportTML, Action.EditTML] + * + * ``` + * @example + * ```js + * hiddenAction: [Action.TML] // hide all TML actions + * disabledActions: [Action.TML] // to disable all TML actions + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: 9.12.0.cl, 10.1.0.sw + */ + Action["TML"] = "tml"; + /** + * The **Create Liveboard* action on + * the Liveboards page and the Pin modal. + * Allows users to create a Liveboard. + * + * @example + * ```js + * hiddenAction: [Action.CreateLiveboard] + * disabledActions: [Action.CreateLiveboard] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot: 10.1.0.cl, 10.1.0.sw + */ + Action["CreateLiveboard"] = "createLiveboard"; + /** + * Action ID for to hide or disable the + * Verified Liveboard banner. + * @example + * ```js + * hiddenAction: [Action.VerifiedLiveboard] + * ``` + * @version SDK: 1.29.0 | ThoughtSpot: 9.10.0.cl, 10.1.0.sw + */ + Action["VerifiedLiveboard"] = "verifiedLiveboard"; + /** + * Action ID for the *Ask Sage* In Natural Language Search embed, + * *Spotter* in Liveboard, full app, and Spotter embed. + * + * Allows initiating a conversation with ThoughtSpot AI analyst. + * + * @example + * ```js + * hiddenAction: [Action.AskAi] + * ``` + * @version SDK: 1.29.0 | ThoughtSpot Cloud: 9.12.0.cl + */ + Action["AskAi"] = "AskAi"; + /** + * The **Add KPI to Watchlist** action on Home page watchlist. + * Adds a KPI chart to the watchlist on the Home page. + * @example + * ```js + * disabledActions: [Action.AddToWatchlist] + * ``` + * @version SDK: 1.27.9 | ThoughtSpot Cloud: 9.12.5.cl + */ + Action["AddToWatchlist"] = "addToWatchlist"; + /** + * The **Remove from watchlist** menu action on KPI watchlist. + * Removes a KPI chart from the watchlist on the Home page. + * @example + * ```js + * disabledActions: [Action.RemoveFromWatchlist] + * ``` + * @version SDK: 1.27.9 | ThoughtSpot: 9.12.5.cl + */ + Action["RemoveFromWatchlist"] = "removeFromWatchlist"; + /** + * The **Organize Favourites** action on Homepage + * *Favorites* module. + * This action is deprecated. Use {@link Action.OrganizeFavorites} instead. + * + * @example + * ```js + * disabledActions: [Action.OrganiseFavourites] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + Action["OrganiseFavourites"] = "organiseFavourites"; + /** + * The **Organize Favorites** action on Homepage + * *Favorites* module. + * + * @example + * ```js + * disabledActions: [Action.OrganizeFavorites] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + Action["OrganizeFavorites"] = "organiseFavourites"; + /** + * The **AI Highlights** action on a Liveboard. + * + * @example + * ```js + * hiddenAction: [Action.AIHighlights] + * ``` + * @version SDK: 1.27.10 | ThoughtSpot Cloud: 9.12.5.cl + */ + Action["AIHighlights"] = "AIHighlights"; + /** + * The *Edit* action on the *Liveboard Schedules* page + * (new Homepage experience). + * Allows editing Liveboard schedules. + * + * @example + * ```js + * disabledActions: [Action.EditScheduleHomepage] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot Cloud: 10.3.0.cl + */ + Action["EditScheduleHomepage"] = "editScheduleHomepage"; + /** + * The *Pause* action on the *Liveboard Schedules* page + * Pauses a scheduled Liveboard job. + * @example + * ```js + * disabledActions: [Action.PauseScheduleHomepage] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot Cloud: 10.3.0.cl + */ + Action["PauseScheduleHomepage"] = "pauseScheduleHomepage"; + /** + * The **View run history** action **Liveboard Schedules** page. + * Allows viewing schedule run history. + * @example + * ```js + * disabledActions: [Action.ViewScheduleRunHomepage] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot: 10.3.0.cl + */ + Action["ViewScheduleRunHomepage"] = "viewScheduleRunHomepage"; + /** + * Action ID to hide or disable the + * unsubscribe option for Liveboard schedules. + * @example + * ```js + * disabledActions: [Action.UnsubscribeScheduleHomepage] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot: 10.3.0.cl + */ + Action["UnsubscribeScheduleHomepage"] = "unsubscribeScheduleHomepage"; + /** + * The **Manage Tags** action on Homepage Favourite Module. + * @example + * ```js + * disabledActions: [Action.ManageTags] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot Cloud: 10.3.0.cl + */ + Action["ManageTags"] = "manageTags"; + /** + * The **Delete** action on the **Liveboard Schedules* page. + * Deletes a Liveboard schedule. + * @example + * ```js + * disabledActions: [Action.DeleteScheduleHomepage] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot: 10.3.0.cl + */ + Action["DeleteScheduleHomepage"] = "deleteScheduleHomepage"; + /** + * The **Analyze CTA** action on KPI chart. + * @example + * ```js + * disabledActions: [Action.KPIAnalysisCTA] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot Cloud: 10.3.0.cl + */ + Action["KPIAnalysisCTA"] = "kpiAnalysisCTA"; + /** + * Action ID for disabling chip reorder in Answer and Liveboard + * @example + * ```js + * const disabledActions = [Action.DisableChipReorder] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + Action["DisableChipReorder"] = "disableChipReorder"; + /** + * Action ID to show, hide, or disable filters + * in a Liveboard tab. + * + * @example + * ```js + * hiddenAction: [Action.ChangeFilterVisibilityInTab] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + Action["ChangeFilterVisibilityInTab"] = "changeFilterVisibilityInTab"; + /** + * The **Data model instructions** button on the Spotter interface. + * Allows opening the data model instructions modal. + * + * @example + * ```js + * hiddenAction: [Action.DataModelInstructions] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["DataModelInstructions"] = "DataModelInstructions"; + /** + * The **Preview data** button on the Spotter interface. + * Allows previewing the data used for Spotter queries. + * + * @example + * ```js + * hiddenAction: [Action.PreviewDataSpotter] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + Action["PreviewDataSpotter"] = "previewDataSpotter"; + /** + * The **Reset** link on the Spotter interface. + * Resets the conversation with Spotter. + * + * @example + * ```js + * hiddenAction: [Action.ResetSpotterChat] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + Action["ResetSpotterChat"] = "resetSpotterChat"; + /** + * Action ID for hide or disable the + * Spotter feedback widget. + * + * @example + * ```js + * hiddenAction: [Action.SpotterFeedback] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + Action["SpotterFeedback"] = "spotterFeedback"; + /** + * Action ID for hide or disable + * the previous prompt edit option in Spotter. + * + * @example + * ```js + * hiddenAction: [Action.EditPreviousPrompt] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + Action["EditPreviousPrompt"] = "editPreviousPrompt"; + /** + * Action ID for hide or disable + * the previous prompt deletion option in Spotter. + * + * @example + * ```js + * hiddenAction: [Action.DeletePreviousPrompt] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + Action["DeletePreviousPrompt"] = "deletePreviousPrompt"; + /** + * Action ID for hide or disable editing tokens generated from + * Spotter results. + * @example + * ```js + * hiddenAction: [Action.EditTokens] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + Action["EditTokens"] = "editTokens"; + /** + * Action ID for hiding rename option for Column rename + * @example + * ```js + * hiddenAction: [Action.ColumnRename] + * ``` + * @version SDK: 1.37.0 | ThoughtSpot Cloud: 10.8.0.cl + */ + Action["ColumnRename"] = "columnRename"; + /** + * Action ID for hide checkboxes for include or exclude + * cover and filter pages in the Liveboard PDF + * @example + * ```js + * hiddenAction: [Action.CoverAndFilterOptionInPDF] + * ``` + * @version SDK: 1.37.0 | ThoughtSpot Cloud: 10.8.0.cl + */ + Action["CoverAndFilterOptionInPDF"] = "coverAndFilterOptionInPDF"; + /** + * Action ID to hide or disable the Coaching workflow in Spotter conversations. + * When disabled, users cannot access **Add to Coaching** workflow in conversation. + * The **Add to Coaching** feature allows adding reference questions and + * business terms to improve Spotter’s responses. This feature is generally available + * (GA) from version 26.2.0.cl and enabled by default on embed deployments. + * @example + * ```js + * hiddenAction: [Action.InConversationTraining] + * disabledActions: [Action.InConversationTraining] + * + * ``` + * @version SDK: 1.39.0 | ThoughtSpot Cloud: 10.10.0.cl + */ + Action["InConversationTraining"] = "InConversationTraining"; + /** + * Action ID to hide the warnings banner in + * Spotter results. It's an EA feature and + * handled by LD. + * @example + * ```js + * hiddenAction: [Action.SpotterWarningsBanner] + * ``` + * @version SDK: 1.41.0 | ThoughtSpot Cloud: 10.13.0.cl + */ + Action["SpotterWarningsBanner"] = "SpotterWarningsBanner"; + /** + * Action ID to hide the warnings border on the knowledge + * card in Spotter results. It's an EA feature and + * handled by LD. + * @example + * ```js + * hiddenAction: [Action.SpotterWarningsOnTokens] + * ``` + * @version SDK: 1.41.0 | ThoughtSpot Cloud: 10.13.0.cl + */ + Action["SpotterWarningsOnTokens"] = "SpotterWarningsOnTokens"; + /** + * Action ID to disable the click event handler on knowledge + * card in Spotter results. It's an EA feature and + * handled by LD. + * @example + * ```js + * hiddenAction: [Action.SpotterTokenQuickEdit] + * ``` + * @version SDK: 1.41.0 | ThoughtSpot Cloud: 10.13.0.cl + */ + Action["SpotterTokenQuickEdit"] = "SpotterTokenQuickEdit"; + /** + * The **PNG screenshot in email** option in the schedule email dialog. + * Includes a PNG screenshot in the notification email body. + * @example + * ```js + * disabledActions: [Action.PngScreenshotInEmail] + * ``` + * ``` + * @version SDK: 1.42.0 | ThoughtSpot Cloud: 10.14.0.cl + */ + Action["PngScreenshotInEmail"] = "pngScreenshotInEmail"; + /** + * The **Remove attachment** action in the schedule email dialog. + * Removes an attachment from the email configuration. + * @example + * ```js + * disabledActions: [Action.RemoveAttachment] + * ``` + * ``` + * ``` + * @version SDK: 1.42.0 | ThoughtSpot Cloud: 10.14.0.cl + */ + Action["RemoveAttachment"] = "removeAttachment"; + /** + * The **Style panel** on a Liveboard. + * Controls the visibility of the Liveboard style panel. + * @example + * ```js + * hiddenActions: [Action.LiveboardStylePanel] + * ``` + * @version SDK: 1.43.0 | ThoughtSpot Cloud: 10.15.0.cl + */ + Action["LiveboardStylePanel"] = "liveboardStylePanel"; + /** + * The **Publish** action for Liveboards, Answers and Models. + * Opens the publishing modal. It's a parent action for the + * **Manage Publishing** and **Unpublish** actions if the object + * is already published, otherwise appears standalone. + * @example + * ```js + * hiddenActions: [Action.Publish] + * disabledActions: [Action.Publish] + * ``` + * @version SDK: 1.45.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Action["Publish"] = "publish"; + /** + * The **Manage Publishing** action for Liveboards, Answers and Models. + * Opens the same publishing modal as the **Publish** action. + * Appears as a child action to the **Publish** action if the + * object is already published. + * @example + * ```js + * hiddenActions: [Action.ManagePublishing] + * disabledActions: [Action.ManagePublishing] + * ``` + * @version SDK: 1.45.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Action["ManagePublishing"] = "managePublishing"; + /** + * The **Unpublish** action for Liveboards, Answers and Models. + * Opens the unpublishing modal. Appears as a child action to + * the **Publish** action if the object is already published. + * @example + * ```js + * hiddenActions: [Action.Unpublish] + * disabledActions: [Action.Unpublish] + * ``` + * @version SDK: 1.45.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Action["Unpublish"] = "unpublish"; + /** + * The **Parameterize** action for Tables and Connections. + * Opens the parameterization modal. + * @example + * ```js + * hiddenActions: [Action.Parameterize] + * disabledActions: [Action.Parameterize] + * ``` + * @version SDK: 1.45.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Action["Parameterize"] = "parameterise"; + /** + * The **Move to Group** menu action on a Liveboard. + * Allows moving a visualization to a different group. + * @example + * ```js + * disabledActions: [Action.MoveToGroup] + * ``` + * @version SDK: 1.44.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Action["MoveToGroup"] = "moveToGroup"; + /** + * The **Move out of Group** menu action on a Liveboard. + * Allows moving a visualization out of a group. + * @example + * ```js + * disabledActions: [Action.MoveOutOfGroup] + * ``` + * @version SDK: 1.44.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Action["MoveOutOfGroup"] = "moveOutOfGroup"; + /** + * The **Create Group** menu action on a Liveboard. + * Allows creating a new group. + * @example + * ```js + * disabledActions: [Action.CreateGroup] + * ``` + * @version SDK: 1.44.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Action["CreateGroup"] = "createGroup"; + /** + * The **Ungroup Liveboard Group** menu action on a Liveboard. + * Allows ungrouping a liveboard group. + * @example + * ```js + * disabledActions: [Action.UngroupLiveboardGroup] + * ``` + * @version SDK: 1.44.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Action["UngroupLiveboardGroup"] = "ungroupLiveboardGroup"; + /** + * Controls visibility of the sidebar header (title and toggle button) + * in the Spotter past conversations sidebar. + * @example + * ```js + * hiddenActions: [Action.SpotterSidebarHeader] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterSidebarHeader"] = "spotterSidebarHeader"; + /** + * Controls visibility of the sidebar footer (documentation link) + * in the Spotter past conversations sidebar. + * @example + * ```js + * hiddenActions: [Action.SpotterSidebarFooter] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterSidebarFooter"] = "spotterSidebarFooter"; + /** + * Controls visibility and disable state of the sidebar toggle/expand button + * in the Spotter past conversations sidebar. + * @example + * ```js + * disabledActions: [Action.SpotterSidebarToggle] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterSidebarToggle"] = "spotterSidebarToggle"; + /** + * Controls visibility and disable state of the "New Chat" button + * in the Spotter past conversations sidebar. + * @example + * ```js + * disabledActions: [Action.SpotterNewChat] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterNewChat"] = "spotterNewChat"; + /** + * Controls visibility of the past conversation banner alert + * in the Spotter interface. + * @example + * ```js + * hiddenActions: [Action.SpotterPastChatBanner] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterPastChatBanner"] = "spotterPastChatBanner"; + /** + * Controls visibility and disable state of the conversation edit menu + * (three-dot menu) in the Spotter past conversations sidebar. + * @example + * ```js + * disabledActions: [Action.SpotterChatMenu] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterChatMenu"] = "spotterChatMenu"; + /** + * Controls visibility and disable state of the rename action + * in the Spotter conversation edit menu. + * @example + * ```js + * disabledActions: [Action.SpotterChatRename] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterChatRename"] = "spotterChatRename"; + /** + * Controls visibility and disable state of the delete action + * in the Spotter conversation edit menu. + * @example + * ```js + * disabledActions: [Action.SpotterChatDelete] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterChatDelete"] = "spotterChatDelete"; + /** + * Controls visibility and disable state of the documentation/best practices + * link in the Spotter sidebar footer. + * @example + * ```js + * disabledActions: [Action.SpotterDocs] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterDocs"] = "spotterDocs"; + /** + * Controls visibility and disable state of the connector resources + * section in the Spotter chat interface. + * @example + * ```js + * hiddenActions: [Action.SpotterChatConnectorResources] + * disabledActions: [Action.SpotterChatConnectorResources] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot Cloud: 26.5.0.cl + */ + Action["SpotterChatConnectorResources"] = "spotterChatConnectorResources"; + /** + * Controls visibility and disable state of the connectors + * in the Spotter chat interface. + * @example + * ```js + * hiddenActions: [Action.SpotterChatConnectors] + * disabledActions: [Action.SpotterChatConnectors] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot Cloud: 26.5.0.cl + */ + Action["SpotterChatConnectors"] = "spotterChatConnectors"; + /** + * Controls visibility and disable state of the mode switcher + * in the Spotter chat interface. + * @example + * ```js + * hiddenActions: [Action.SpotterChatModeSwitcher] + * disabledActions: [Action.SpotterChatModeSwitcher] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot Cloud: 26.5.0.cl + */ + Action["SpotterChatModeSwitcher"] = "spotterChatModeSwitcher"; + /** + * The **Include current period** checkbox for date filters. + * Controls the visibility and availability of the option to include + * the current time period in filter results. + * @example + * ```js + * hiddenActions: [Action.IncludeCurrentPeriod] + * disabledActions: [Action.IncludeCurrentPeriod] + * ``` + * @version SDK: 1.45.0 | ThoughtSpot: 26.4.0.cl + */ + Action["IncludeCurrentPeriod"] = "includeCurrentPeriod"; + /** + * The **Send Test Email** button in the Liveboard schedule modal. + * Allows sending a test schedule email to self or all recipients. + * Requires `isSendNowLiveboardSchedulingEnabled` to be enabled. + * @example + * ```js + * disabledActions: [Action.SendTestScheduleEmail] + * hiddenActions: [Action.SendTestScheduleEmail] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot Cloud: 26.5.0.cl + */ + Action["SendTestScheduleEmail"] = "sendTestScheduleEmail"; + /** + * The thumbs up/down feedback buttons in the SpotterViz panel. + * Visible by default. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * hiddenActions: [Action.SpotterVizFeedback] + * disabledActions: [Action.SpotterVizFeedback] + * ``` + */ + Action["SpotterVizFeedback"] = "spotterVizFeedback"; + /** + * The version restore button on checkpoint cards in the SpotterViz panel. + * Visible by default. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * hiddenActions: [Action.SpotterVizCheckpointRestore] + * disabledActions: [Action.SpotterVizCheckpointRestore] + * ``` + */ + Action["SpotterVizCheckpointRestore"] = "spotterVizCheckpointRestore"; + /** + * The **SpotterViz** button in the top edit header. + * Visible by default. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * hiddenActions: [Action.SpotterViz] + * disabledActions: [Action.SpotterViz] + * ``` + */ + Action["SpotterViz"] = "spotterViz"; + /** + * Clears browser cache and fetches new data for liveboard ChartViz Containers. + * Requires `enableLiveboardDataCache` to be enabled. + * @example + * ```js + * disabledActions: [Action.RefreshLiveboardBrowserCache] + * hiddenActions: [Action.RefreshLiveboardBrowserCache] + * ``` + * @version SDK: 1.49.0 | ThoughtSpot Cloud: 26.6.0.cl + */ + Action["RefreshLiveboardBrowserCache"] = "refreshLiveboardBrowserCache"; + })(exports.Action || (exports.Action = {})); + var PrefetchFeatures; + (function (PrefetchFeatures) { + PrefetchFeatures["FullApp"] = "FullApp"; + PrefetchFeatures["SearchEmbed"] = "SearchEmbed"; + PrefetchFeatures["LiveboardEmbed"] = "LiveboardEmbed"; + PrefetchFeatures["VizEmbed"] = "VizEmbed"; + })(PrefetchFeatures || (PrefetchFeatures = {})); + /** + * Enum for options to change context trigger. + * The `BOTH_CLICKS` option is available from 10.8.0.cl. + */ + var ContextMenuTriggerOptions; + (function (ContextMenuTriggerOptions) { + ContextMenuTriggerOptions["LEFT_CLICK"] = "left-click"; + ContextMenuTriggerOptions["RIGHT_CLICK"] = "right-click"; + ContextMenuTriggerOptions["BOTH_CLICKS"] = "both-clicks"; + })(ContextMenuTriggerOptions || (ContextMenuTriggerOptions = {})); + /** + * Enum options to show custom actions at different + * positions in the application. + */ + exports.CustomActionsPosition = void 0; + (function (CustomActionsPosition) { + /** + * Shows the action as a primary button + * in the toolbar area of the embed. + */ + CustomActionsPosition["PRIMARY"] = "PRIMARY"; + /** + * Shows the action inside the "More" menu + * (three-dot overflow menu). + */ + CustomActionsPosition["MENU"] = "MENU"; + /** + * Shows the action in the right-click + * context menu. Only supported for + * {@link CustomActionTarget.VIZ}, + * {@link CustomActionTarget.ANSWER}, and + * {@link CustomActionTarget.SPOTTER} targets. + */ + CustomActionsPosition["CONTEXTMENU"] = "CONTEXTMENU"; + })(exports.CustomActionsPosition || (exports.CustomActionsPosition = {})); + /** + * Enum options to mention the target of the code-based custom action. + * The target determines which type of ThoughtSpot object the action is + * associated with, and also controls which positions and scoping options + * are available. + */ + var CustomActionTarget; + (function (CustomActionTarget) { + /** + * Action applies at the Liveboard level. + * Supported positions: + * {@link CustomActionsPosition.PRIMARY}, + * {@link CustomActionsPosition.MENU}. + * Can be scoped with + * `metadataIds.liveboardIds`, + * `orgIds`, and `groupIds`. + */ + CustomActionTarget["LIVEBOARD"] = "LIVEBOARD"; + /** + * Action applies to individual + * visualizations (charts/tables). + * Supported positions: + * {@link CustomActionsPosition.PRIMARY}, + * {@link CustomActionsPosition.MENU}, + * {@link CustomActionsPosition.CONTEXTMENU}. + * Can be scoped with `metadataIds` + * (answerIds, liveboardIds, vizIds), + * `dataModelIds` (modelIds, + * modelColumnNames), `orgIds`, + * and `groupIds`. + */ + CustomActionTarget["VIZ"] = "VIZ"; + /** + * Action applies to saved or unsaved + * Answers. Supported positions: + * {@link CustomActionsPosition.PRIMARY}, + * {@link CustomActionsPosition.MENU}, + * {@link CustomActionsPosition.CONTEXTMENU}. + * Can be scoped with + * `metadataIds.answerIds`, + * `dataModelIds` (modelIds, + * modelColumnNames), `orgIds`, + * and `groupIds`. + */ + CustomActionTarget["ANSWER"] = "ANSWER"; + /** + * Action applies to Spotter + * (AI-powered search). + * Supported positions: + * {@link CustomActionsPosition.MENU}, + * {@link CustomActionsPosition.CONTEXTMENU}. + * Can be scoped with + * `dataModelIds.modelIds`, + * `orgIds`, and `groupIds`. + */ + CustomActionTarget["SPOTTER"] = "SPOTTER"; + })(CustomActionTarget || (CustomActionTarget = {})); + /** + * Enum options to show or suppress Visual Embed SDK and + * ThoughtSpot application logs in the console output. + * This attribute doesn't support suppressing + * browser warnings or errors. + */ + exports.LogLevel = void 0; + (function (LogLevel) { + /** + * No application or SDK-related logs will be logged + * in the console output. + * @example + * ```js + * init({ + * ... //other embed view config, + * logLevel: LogLevel.SILENT, + * }) + * ``` + * @version SDK: 1.26.7 | ThoughtSpot Cloud: 9.10.0.cl + */ + LogLevel["SILENT"] = "SILENT"; + /** + * Log only errors in the console output. + * @example + * ```js + * init({ + * ... //other embed view config, + * logLevel: LogLevel.ERROR, + * }) + * ``` + * @version SDK: 1.26.7 | ThoughtSpot Cloud: 9.10.0.cl + */ + LogLevel["ERROR"] = "ERROR"; + /** + * Log only warnings and errors in the console output. + * @example + * ```js + * init({ + * ... //other embed view config, + * logLevel: LogLevel.WARN, + * }) + * ``` + * @version SDK: 1.26.7 | ThoughtSpot Cloud: 9.10.0.cl + */ + LogLevel["WARN"] = "WARN"; + /** + * Log only the information alerts, warnings, and errors + * in the console output. + * @example + * ```js + * init({ + * ... //other embed view config, + * logLevel: LogLevel.INFO, + * }) + * ``` + * @version SDK: 1.26.7 | ThoughtSpot Cloud: 9.10.0.cl + */ + LogLevel["INFO"] = "INFO"; + /** + * Log debug messages, warnings, information alerts, + * and errors in the console output. + * @example + * ```js + * init({ + * ... //other embed view config, + * logLevel: LogLevel.DEBUG, + * }) + * ``` + * @version SDK: 1.26.7 | ThoughtSpot Cloud: 9.10.0.cl + */ + LogLevel["DEBUG"] = "DEBUG"; + /** + * All logs will be logged in the browser console. + * @example + * ```js + * init({ + * ... //other embed view config, + * logLevel: LogLevel.TRACE, + * }) + * ``` + * @version SDK: 1.26.7 | ThoughtSpot Cloud: 9.10.0.cl + */ + LogLevel["TRACE"] = "TRACE"; + })(exports.LogLevel || (exports.LogLevel = {})); + /** + * Error types emitted by embedded components. + * + * These enum values categorize different types of errors that can occur during + * the lifecycle of an embedded ThoughtSpot component. + * Use {@link EmbedErrorDetailsEvent} and {@link EmbedErrorCodes} to handle specific errors. + * @version SDK: 1.44.2 | ThoughtSpot: 26.2.0.cl + * @group Error Handling + * + * @example + * Handle specific error types + * ```js + * embed.on(EmbedEvent.Error, (error) => { + * switch (error.errorType) { + * case ErrorDetailsTypes.API: + * console.error('API error:', error.message); + * break; + * case ErrorDetailsTypes.VALIDATION_ERROR: + * console.error('Validation error:', error.message); + * break; + * case ErrorDetailsTypes.NETWORK: + * console.error('Network error:', error.message); + * break; + * default: + * console.error('Unknown error:', error); + * } + * }); + * ``` + */ + var ErrorDetailsTypes; + (function (ErrorDetailsTypes) { + /** API call failure */ + ErrorDetailsTypes["API"] = "API"; + /** General validation error */ + ErrorDetailsTypes["VALIDATION_ERROR"] = "VALIDATION_ERROR"; + /** Network connectivity or request error */ + ErrorDetailsTypes["NETWORK"] = "NETWORK"; + })(ErrorDetailsTypes || (ErrorDetailsTypes = {})); + /** + * Error codes for identifying specific issues in embedded ThoughtSpot components. Use + * {@link EmbedErrorDetailsEvent} and {@link ErrorDetailsTypes} codes for precise error + * handling and debugging. + * + * @version SDK: 1.44.2 | ThoughtSpot: 26.2.0.cl + * @group Error Handling + + * @example + * Handle specific error codes in the error event handler + * ```js + * embed.on(EmbedEvent.Error, (error) => { + * switch (error.code) { + * case EmbedErrorCodes.WORKSHEET_ID_NOT_FOUND: + * console.error('Worksheet ID not found:', error.message); + * break; + * case EmbedErrorCodes.LIVEBOARD_ID_MISSING: + * console.error('Liveboard ID is missing:', error.message); + * break; + * case EmbedErrorCodes.CONFLICTING_ACTIONS_CONFIG: + * console.error('Conflicting actions configuration:', error.message); + * break; + * case EmbedErrorCodes.CONFLICTING_TABS_CONFIG: + * console.error('Conflicting tabs configuration:', error.message); + * break; + * default: + * console.error('Unknown error:', error); + * } + * }); + * ``` + * */ + var EmbedErrorCodes; + (function (EmbedErrorCodes) { + /** Worksheet ID not found or does not exist */ + EmbedErrorCodes["WORKSHEET_ID_NOT_FOUND"] = "WORKSHEET_ID_NOT_FOUND"; + /** Required Liveboard ID is missing from configuration */ + EmbedErrorCodes["LIVEBOARD_ID_MISSING"] = "LIVEBOARD_ID_MISSING"; + /** Conflicting action configuration detected */ + EmbedErrorCodes["CONFLICTING_ACTIONS_CONFIG"] = "CONFLICTING_ACTIONS_CONFIG"; + /** Conflicting tab configuration detected */ + EmbedErrorCodes["CONFLICTING_TABS_CONFIG"] = "CONFLICTING_TABS_CONFIG"; + /** Error during component initialization */ + EmbedErrorCodes["INIT_ERROR"] = "INIT_ERROR"; + /** Network connectivity or request error */ + EmbedErrorCodes["NETWORK_ERROR"] = "NETWORK_ERROR"; + /** Custom action validation failed */ + EmbedErrorCodes["CUSTOM_ACTION_VALIDATION"] = "CUSTOM_ACTION_VALIDATION"; + /** Authentication/login failed */ + EmbedErrorCodes["LOGIN_FAILED"] = "LOGIN_FAILED"; + /** Render method was not called before attempting to use the component */ + EmbedErrorCodes["RENDER_NOT_CALLED"] = "RENDER_NOT_CALLED"; + /** Host event type is undefined or invalid */ + EmbedErrorCodes["HOST_EVENT_TYPE_UNDEFINED"] = "HOST_EVENT_TYPE_UNDEFINED"; + /** Error parsing api intercept body */ + EmbedErrorCodes["PARSING_API_INTERCEPT_BODY_ERROR"] = "PARSING_API_INTERCEPT_BODY_ERROR"; + /** Failed to update embed parameters during pre-render */ + EmbedErrorCodes["UPDATE_PARAMS_FAILED"] = "UPDATE_PARAMS_FAILED"; + /** Invalid URL provided in configuration */ + EmbedErrorCodes["INVALID_URL"] = "INVALID_URL"; + /** Host event payload validation failed */ + EmbedErrorCodes["HOST_EVENT_VALIDATION"] = "HOST_EVENT_VALIDATION"; + /** UpdateFilters payload is invalid - missing or malformed filter/filters */ + EmbedErrorCodes["UPDATEFILTERS_INVALID_PAYLOAD"] = "UPDATEFILTERS_INVALID_PAYLOAD"; + /** DrillDown payload is invalid - missing or malformed points */ + EmbedErrorCodes["DRILLDOWN_INVALID_PAYLOAD"] = "DRILLDOWN_INVALID_PAYLOAD"; + })(EmbedErrorCodes || (EmbedErrorCodes = {})); + /** + * Context types for specifying the page context when triggering host events. + * Used as the third parameter in the `trigger` method to help ThoughtSpot + * understand the current page context for better event handling. + * + * @example + * ```js + * import { HostEvent, ContextType } from '@thoughtspot/visual-embed-sdk'; + * + * // Trigger an event with specific context + * embed.trigger(HostEvent.Pin, { vizId: "123", liveboardId: "456" }, ContextType.Search); + * ``` + * + * @version SDK: 1.45.2 | ThoughtSpot: 26.3.0.cl + * @group Events + */ + var ContextType; + (function (ContextType) { + /** + * Search answer context for search page or edit viz dialog on liveboard page. + */ + ContextType["Search"] = "search-answer"; + /** + * Liveboard context for liveboard page. + */ + ContextType["Liveboard"] = "liveboard"; + /** + * Answer context for explore modal/page on liveboard page. + */ + ContextType["Answer"] = "answer"; + /** + * Spotter context for spotter modal/page. + */ + ContextType["Spotter"] = "spotter"; + /** + * Other context for any other generic page. + */ + ContextType["Other"] = "other"; + })(ContextType || (ContextType = {})); + /** + * Enum for the type of API intercepted + */ + var InterceptedApiType; + (function (InterceptedApiType) { + /** + * The apis that are use to get the data for the embed + */ + InterceptedApiType["AnswerData"] = "AnswerData"; + /** + * This will intercept all the apis + */ + InterceptedApiType["ALL"] = "ALL"; + /** + * The apis that are use to get the data for the liveboard + */ + InterceptedApiType["LiveboardData"] = "LiveboardData"; + })(InterceptedApiType || (InterceptedApiType = {})); + /** + * Data label filter operators + * @group Visual Overrides + */ + var DataLabelFilterOperator; + (function (DataLabelFilterOperator) { + /** Greater than */ + DataLabelFilterOperator["GreaterThan"] = "GREATER_THAN"; + /** Less than */ + DataLabelFilterOperator["LessThan"] = "LESS_THAN"; + /** Greater than or equal to */ + DataLabelFilterOperator["GreaterThanOrEqualTo"] = "GREATER_THAN_OR_EQUAL_TO"; + /** Less than or equal to */ + DataLabelFilterOperator["LessThanOrEqualTo"] = "LESS_THAN_OR_EQUAL_TO"; + /** Equal to */ + DataLabelFilterOperator["EqualTo"] = "EQUAL_TO"; + /** Not equal to */ + DataLabelFilterOperator["NotEqualTo"] = "NOT_EQUAL_TO"; + })(DataLabelFilterOperator || (DataLabelFilterOperator = {})); + /** + * Conditional formatting operators + * @group Visual Overrides + */ + var ConditionalFormattingOperator; + (function (ConditionalFormattingOperator) { + /** Is equal to */ + ConditionalFormattingOperator["Is"] = "IS"; + /** Is not equal to */ + ConditionalFormattingOperator["IsNot"] = "IS_NOT"; + /** Contains */ + ConditionalFormattingOperator["Contains"] = "CONTAINS"; + /** Does not contain */ + ConditionalFormattingOperator["DoesNotContain"] = "DOES_NOT_CONTAIN"; + /** Starts with */ + ConditionalFormattingOperator["StartsWith"] = "STARTS_WITH"; + /** Ends with */ + ConditionalFormattingOperator["EndsWith"] = "ENDS_WITH"; + /** Greater than */ + ConditionalFormattingOperator["GreaterThan"] = "GREATER_THAN"; + /** Less than */ + ConditionalFormattingOperator["LessThan"] = "LESS_THAN"; + /** Greater than or equal to */ + ConditionalFormattingOperator["GreaterThanEqualTo"] = "GREATER_THAN_EQUAL_TO"; + /** Less than or equal to */ + ConditionalFormattingOperator["LessThanEqualTo"] = "LESS_THAN_EQUAL_TO"; + /** Equal to */ + ConditionalFormattingOperator["EqualTo"] = "EQUAL_TO"; + /** Not equal to */ + ConditionalFormattingOperator["NotEqualTo"] = "NOT_EQUAL_TO"; + /** Is between */ + ConditionalFormattingOperator["IsBetween"] = "IS_BETWEEN"; + /** Is null */ + ConditionalFormattingOperator["IsNull"] = "IS_NULL"; + /** Is not null */ + ConditionalFormattingOperator["IsNotNull"] = "IS_NOT_NULL"; + })(ConditionalFormattingOperator || (ConditionalFormattingOperator = {})); + /** + * Background format types for conditional formatting + * @group Visual Overrides + */ + var BackgroundFormatType; + (function (BackgroundFormatType) { + /** Solid color background */ + BackgroundFormatType["Solid"] = "SOLID"; + /** Gradient background */ + BackgroundFormatType["Gradient"] = "GRADIENT"; + })(BackgroundFormatType || (BackgroundFormatType = {})); + /** + * Comparison types for conditional formatting + * @group Visual Overrides + */ + var ConditionalFormattingComparisonType; + (function (ConditionalFormattingComparisonType) { + /** Value-based comparison */ + ConditionalFormattingComparisonType["ValueBased"] = "VALUE_BASED"; + /** Column-based comparison */ + ConditionalFormattingComparisonType["ColumnBased"] = "COLUMN_BASED"; + /** Parameter-based comparison */ + ConditionalFormattingComparisonType["ParameterBased"] = "PARAMETER_BASED"; + })(ConditionalFormattingComparisonType || (ConditionalFormattingComparisonType = {})); + /** + * Legend position options + * @group Visual Overrides + */ + var LegendPosition; + (function (LegendPosition) { + /** Position legend at the top */ + LegendPosition["Top"] = "top"; + /** Position legend at the bottom */ + LegendPosition["Bottom"] = "bottom"; + /** Position legend on the left */ + LegendPosition["Left"] = "left"; + /** Position legend on the right */ + LegendPosition["Right"] = "right"; + })(LegendPosition || (LegendPosition = {})); + /** + * Table theme options + * @group Visual Overrides + */ + var TableTheme; + (function (TableTheme) { + /** Outline theme */ + TableTheme["Outline"] = "OUTLINE"; + /** Row theme */ + TableTheme["Row"] = "ROW"; + /** Zebra theme */ + TableTheme["Zebra"] = "ZEBRA"; + })(TableTheme || (TableTheme = {})); + /** + * Table content density options + * @group Visual Overrides + */ + var TableContentDensity; + (function (TableContentDensity) { + /** Regular density */ + TableContentDensity["Regular"] = "REGULAR"; + /** Compact density */ + TableContentDensity["Compact"] = "COMPACT"; + })(TableContentDensity || (TableContentDensity = {})); + + var version="1.48.0";var packageInfo = {version:version}; + + const logFunctions = { + [exports.LogLevel.SILENT]: () => undefined, + [exports.LogLevel.ERROR]: console.error, + [exports.LogLevel.WARN]: console.warn, + [exports.LogLevel.INFO]: console.info, + [exports.LogLevel.DEBUG]: console.debug, + [exports.LogLevel.TRACE]: console.trace, + }; + let globalLogLevelOverride = exports.LogLevel.TRACE; + const setGlobalLogLevelOverride = (logLevel) => { + globalLogLevelOverride = logLevel; + }; + const logLevelToNumber = { + [exports.LogLevel.SILENT]: 0, + [exports.LogLevel.ERROR]: 1, + [exports.LogLevel.WARN]: 2, + [exports.LogLevel.INFO]: 3, + [exports.LogLevel.DEBUG]: 4, + [exports.LogLevel.TRACE]: 5, + }; + const compareLogLevels = (logLevel1, logLevel2) => { + const logLevel1Index = logLevelToNumber[logLevel1]; + const logLevel2Index = logLevelToNumber[logLevel2]; + return logLevel1Index - logLevel2Index; + }; + class Logger { + constructor() { + this.logLevel = exports.LogLevel.ERROR; + this.setLogLevel = (newLogLevel) => { + this.logLevel = newLogLevel; + }; + this.getLogLevel = () => this.logLevel; + } + canLog(logLevel) { + if (logLevel === exports.LogLevel.SILENT) + return false; + if (globalLogLevelOverride !== undefined) { + return compareLogLevels(globalLogLevelOverride, logLevel) >= 0; + } + return compareLogLevels(this.logLevel, logLevel) >= 0; + } + logMessages(args, logLevel) { + if (this.canLog(logLevel)) { + const logFn = logFunctions[logLevel]; + if (logFn) { + logFn(`[vesdk-${version}]`, ...args); + } + } + } + log(...args) { + this.info(args); + } + info(...args) { + this.logMessages(args, exports.LogLevel.INFO); + } + debug(...args) { + this.logMessages(args, exports.LogLevel.DEBUG); + } + trace(...args) { + this.logMessages(args, exports.LogLevel.TRACE); + } + error(...args) { + this.logMessages(args, exports.LogLevel.ERROR); + } + warn(...args) { + this.logMessages(args, exports.LogLevel.WARN); + } + } + const logger$3 = new Logger(); + + const ERROR_MESSAGE = { + INVALID_THOUGHTSPOT_HOST: 'Error parsing ThoughtSpot host. Please provide a valid URL.', + SPOTTER_EMBED_WORKSHEED_ID_NOT_FOUND: 'Please select a Model to get started', + LIVEBOARD_VIZ_ID_VALIDATION: 'Please select a Liveboard to embed.', + TRIGGER_TIMED_OUT: 'Trigger timed-out in getting a response', + SEARCHEMBED_BETA_WRANING_MESSAGE: 'SearchEmbed is in Beta in this release.', + THIRD_PARTY_COOKIE_BLOCKED_ALERT: 'Third-party cookie access is blocked on this browser. Please allow third-party cookies for this to work properly. \nYou can use `suppressNoCookieAccessAlert` to suppress this message.', + DUPLICATE_TOKEN_ERR: 'Duplicate token. Please issue a new token every time getAuthToken callback is called. See https://developers.thoughtspot.com/docs/?pageid=embed-auth#trusted-auth-embed for more details.', + SDK_NOT_INITIALIZED: 'SDK not initialized', + SESSION_INFO_FAILED: 'Failed to get session information', + INVALID_TOKEN_ERROR: 'Received invalid token from getAuthToken callback or authToken endpoint.', + INVALID_TOKEN_TYPE_ERROR: 'Expected getAuthToken to return a string, but received a {invalidType}.', + MIXPANEL_TOKEN_NOT_FOUND: 'Mixpanel token not found in session info', + PRERENDER_ID_MISSING: 'PreRender ID is required for preRender', + SYNC_STYLE_CALLED_BEFORE_RENDER: 'PreRender should be called before using syncPreRenderStyle', + CSP_VIOLATION_ALERT: 'CSP violation detected. Please check the console errors for more details.', + CSP_FRAME_HOST_VIOLATION_LOG_MESSAGE: 'Please set up CSP correctly for the application to start working. For more information, see https://developers.thoughtspot.com/docs/security-settings#csp-viz-embed-hosts. \n If the issue persists, refer to https://developers.thoughtspot.com/docs/security-settings#csp-viz-embed-hosts', + MISSING_REPORTING_OBSERVER: 'ReportingObserver not supported', + RENDER_CALLED_BEFORE_INIT: 'Looks like render was called before calling init, the render won\'t start until init is called.\nFor more info check\n1. https://developers.thoughtspot.com/docs/Function_init#_init\n2.https://developers.thoughtspot.com/docs/getting-started#initSdk', + SPOTTER_AGENT_NOT_INITIALIZED: 'SpotterAgent not initialized', + OFFLINE_WARNING: 'Network not Detected. Embed is offline. Please reconnect and refresh', + INIT_SDK_REQUIRED: 'You need to init the ThoughtSpot SDK module first', + CONFLICTING_ACTIONS_CONFIG: 'You cannot have both hidden actions and visible actions', + CONFLICTING_TABS_CONFIG: 'You cannot have both hidden Tabs and visible Tabs', + RENDER_BEFORE_EVENTS_REQUIRED: 'Please call render before triggering events', + HOST_EVENT_TYPE_UNDEFINED: 'Host event type is undefined', + LOGIN_FAILED: 'Login failed', + ERROR_PARSING_API_INTERCEPT_BODY: 'Error parsing api intercept body', + SSR_ENVIRONMENT_ERROR: 'SSR environment detected. This function cannot be called in SSR environment.', + UPDATE_PARAMS_FAILED: 'Failed to update embed parameters', + INVALID_SPOTTER_DOCUMENTATION_URL: 'Invalid spotterDocumentationUrl. Please provide a valid http or https URL.', + UPDATEFILTERS_INVALID_PAYLOAD: 'UpdateFilters requires a valid filter or filters array. Expected: { filter: { column, oper, values } } or { filters: [{ column, oper, values }, ...] }', + DRILLDOWN_INVALID_PAYLOAD: 'DrillDown requires a valid points object. Expected: { points: { clickedPoint?, selectedPoints? }, autoDrillDown?, vizId? }', + }; + const CUSTOM_ACTIONS_ERROR_MESSAGE = { + INVALID_ACTION_OBJECT: 'Custom Action Validation Error: Invalid action object provided', + MISSING_REQUIRED_FIELDS: (id, missingFields) => `Custom Action Validation Error for '${id}': Missing required fields: ${missingFields.join(', ')}`, + UNSUPPORTED_TARGET: (id, targetType) => `Custom Action Validation Error for '${id}': Target type '${targetType}' is not supported`, + INVALID_POSITION: (position, targetType, supportedPositions) => `Position '${position}' is not supported for ${targetType.toLowerCase()}-level custom actions. Supported positions: ${supportedPositions}`, + INVALID_METADATA_IDS: (targetType, invalidIds, supportedIds) => `Invalid metadata IDs for ${targetType.toLowerCase()}-level custom actions: ${invalidIds.join(', ')}. Supported metadata IDs: ${supportedIds}`, + INVALID_DATA_MODEL_IDS: (targetType, invalidIds, supportedIds) => `Invalid data model IDs for ${targetType.toLowerCase()}-level custom actions: ${invalidIds.join(', ')}. Supported data model IDs: ${supportedIds}`, + INVALID_FIELDS: (targetType, invalidFields, supportedFields) => `Invalid fields for ${targetType.toLowerCase()}-level custom actions: ${invalidFields.join(', ')}. Supported fields: ${supportedFields}`, + DUPLICATE_IDS: (id, duplicateNames, keptName) => `Duplicate custom action ID '${id}' found. Actions with names '${duplicateNames.join("', '")}' will be ignored. Keeping '${keptName}'.`, + }; + + /** + * Copyright (c) 2023 + * + * Common utility functions for ThoughtSpot Visual Embed SDK + * @summary Utils + * @author Ayon Ghosh + */ + /** + * Construct a runtime filters query string from the given filters. + * Refer to the following docs for more details on runtime filter syntax: + * https://cloud-docs.thoughtspot.com/admin/ts-cloud/apply-runtime-filter.html + * https://cloud-docs.thoughtspot.com/admin/ts-cloud/runtime-filter-operators.html + * @param runtimeFilters + */ + const getFilterQuery = (runtimeFilters) => { + if (runtimeFilters && runtimeFilters.length) { + const filters = runtimeFilters.map((filter, valueIndex) => { + const index = valueIndex + 1; + const filterExpr = []; + filterExpr.push(`col${index}=${encodeURIComponent(filter.columnName)}`); + filterExpr.push(`op${index}=${filter.operator}`); + filterExpr.push(filter.values.map((value) => { + const encodedValue = typeof value === 'bigint' ? value.toString() : value; + return `val${index}=${encodeURIComponent(String(encodedValue))}`; + }).join('&')); + return filterExpr.join('&'); + }); + return `${filters.join('&')}`; + } + return null; + }; + /** + * Construct a runtime parameter override query string from the given option. + * @param runtimeParameters + */ + const getRuntimeParameters = (runtimeParameters) => { + if (runtimeParameters && runtimeParameters.length) { + const params = runtimeParameters.map((param, valueIndex) => { + const index = valueIndex + 1; + const filterExpr = []; + filterExpr.push(`param${index}=${encodeURIComponent(param.name)}`); + filterExpr.push(`paramVal${index}=${encodeURIComponent(param.value)}`); + return filterExpr.join('&'); + }); + return `${params.join('&')}`; + } + return null; + }; + /** + * Convert a value to a string representation to be sent as a query + * parameter to the ThoughtSpot app. + * @param value Any parameter value + */ + const serializeParam = (value) => { + // do not serialize primitive types + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + return value; + } + return JSON.stringify(value); + }; + /** + * Convert a value to a string: + * in case of an array, we convert it to CSV. + * in case of any other type, we directly return the value. + * @param value + */ + const paramToString = (value) => (Array.isArray(value) ? value.join(',') : value); + /** + * Return a query param string composed from the given params object + * @param queryParams + * @param shouldSerializeParamValues + */ + const getQueryParamString = (queryParams, shouldSerializeParamValues = false) => { + const qp = []; + const params = Object.keys(queryParams); + params.forEach((key) => { + const val = queryParams[key]; + if (val !== undefined) { + const serializedValue = shouldSerializeParamValues + ? serializeParam(val) + : paramToString(val); + qp.push(`${key}=${serializedValue}`); + } + }); + if (qp.length) { + return qp.join('&'); + } + return null; + }; + /** + * Get a string representation of a dimension value in CSS + * If numeric, it is considered in pixels. + * @param value + */ + const getCssDimension = (value) => { + if (typeof value === 'number') { + return `${value}px`; + } + return value; + }; + /** + * Validates if a string is a valid CSS margin value. + * @param value - The string to validate + * @returns true if the value is a valid CSS margin value, false otherwise + */ + const isValidCssMargin = (value) => { + if (isUndefined(value)) { + return false; + } + if (typeof value !== 'string') { + logger$3.error('Please provide a valid lazyLoadingMargin value (e.g., "10px")'); + return false; + } + // This pattern allows for an optional negative sign, and numbers + // that can be integers or decimals (including leading dot). + const cssUnitPattern = /^-?(\d+(\.\d*)?|\.\d+)(px|em|rem|%|vh|vw)$/i; + const parts = value.trim().split(/\s+/); + if (parts.length > 4) { + logger$3.error('Please provide a valid lazyLoadingMargin value (e.g., "10px")'); + return false; + } + const isValid = parts.every(part => { + const trimmedPart = part.trim(); + return trimmedPart.toLowerCase() === 'auto' || trimmedPart === '0' || cssUnitPattern.test(trimmedPart); + }); + if (!isValid) { + logger$3.error('Please provide a valid lazyLoadingMargin value (e.g., "10px")'); + return false; + } + return true; + }; + const getSSOMarker = (markerId) => { + const encStringToAppend = encodeURIComponent(markerId); + return `tsSSOMarker=${encStringToAppend}`; + }; + /** + * Append a string to a URL's hash fragment + * @param url A URL + * @param stringToAppend The string to append to the URL hash + */ + const appendToUrlHash = (url, stringToAppend) => { + let outputUrl = url; + const encStringToAppend = encodeURIComponent(stringToAppend); + const marker = `tsSSOMarker=${encStringToAppend}`; + let splitAdder = ''; + if (url.indexOf('#') >= 0) { + // If second half of hash contains a '?' already add a '&' instead of + // '?' which appends to query params. + splitAdder = url.split('#')[1].indexOf('?') >= 0 ? '&' : '?'; + } + else { + splitAdder = '#?'; + } + outputUrl = `${outputUrl}${splitAdder}${marker}`; + return outputUrl; + }; + /** + * + * @param url + * @param stringToAppend + * @param path + */ + function getRedirectUrl(url, stringToAppend, path = '') { + const targetUrl = path ? new URL(path, window.location.origin).href : url; + return appendToUrlHash(targetUrl, stringToAppend); + } + const getEncodedQueryParamsString = (queryString) => { + if (!queryString) { + return queryString; + } + return btoa(queryString).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); + }; + const getOffsetTop = (element) => { + const rect = element.getBoundingClientRect(); + return rect.top + window.scrollY; + }; + const embedEventStatus = { + START: 'start', + END: 'end', + }; + const setAttributes = (element, attributes) => { + Object.keys(attributes).forEach((key) => { + element.setAttribute(key, attributes[key].toString()); + }); + }; + const isCloudRelease = (version) => version.endsWith('.cl'); + /* For Search Embed: ReleaseVersionInBeta */ + const checkReleaseVersionInBeta = (releaseVersion, suppressBetaWarning) => { + if (releaseVersion !== '' && !isCloudRelease(releaseVersion)) { + const splittedReleaseVersion = releaseVersion.split('.'); + const majorVersion = Number(splittedReleaseVersion[0]); + const isBetaVersion = majorVersion < 8; + return !suppressBetaWarning && isBetaVersion; + } + return false; + }; + const getCustomisations = (embedConfig, viewConfig) => { + const customizationsFromViewConfig = viewConfig.customizations; + const customizationsFromEmbedConfig = embedConfig.customizations + || embedConfig.customisations; + const customizations = { + style: { + ...customizationsFromEmbedConfig?.style, + ...customizationsFromViewConfig?.style, + customCSS: { + ...customizationsFromEmbedConfig?.style?.customCSS, + ...customizationsFromViewConfig?.style?.customCSS, + }, + customCSSUrl: customizationsFromViewConfig?.style?.customCSSUrl + || customizationsFromEmbedConfig?.style?.customCSSUrl, + }, + content: { + ...customizationsFromEmbedConfig?.content, + ...customizationsFromViewConfig?.content, + }, + }; + return customizations; + }; + const getRuntimeFilters = (runtimefilters) => getFilterQuery(runtimefilters || []); + /** + * Gets a reference to the DOM node given + * a selector. + * @param domSelector + */ + function getDOMNode(domSelector) { + return typeof domSelector === 'string' ? document.querySelector(domSelector) : domSelector; + } + const deepMerge = (target, source) => merge(target, source); + const getOperationNameFromQuery = (query) => { + const regex = /(?:query|mutation)\s+(\w+)/; + const matches = query.match(regex); + return matches?.[1]; + }; + /** + * + * @param obj + */ + function removeTypename(obj) { + if (!obj || typeof obj !== 'object') + return obj; + for (const key in obj) { + if (key === '__typename') { + delete obj[key]; + } + else if (typeof obj[key] === 'object') { + removeTypename(obj[key]); + } + } + return obj; + } + /** + * Sets the specified style properties on an HTML element. + * @param {HTMLElement} element - The HTML element to which the styles should be applied. + * @param {Partial} styleProperties - An object containing style + * property names and their values. + * @example + * // Apply styles to an element + * const element = document.getElementById('myElement'); + * const styles = { + * backgroundColor: 'red', + * fontSize: '16px', + * }; + * setStyleProperties(element, styles); + */ + const setStyleProperties = (element, styleProperties) => { + if (!element?.style) + return; + Object.keys(styleProperties).forEach((styleProperty) => { + const styleKey = styleProperty; + const value = styleProperties[styleKey]; + if (value !== undefined) { + element.style[styleKey] = value.toString(); + } + }); + }; + /** + * Removes specified style properties from an HTML element. + * @param {HTMLElement} element - The HTML element from which the styles should be removed. + * @param {string[]} styleProperties - An array of style property names to be removed. + * @example + * // Remove styles from an element + * const element = document.getElementById('myElement'); + * element.style.backgroundColor = 'red'; + * const propertiesToRemove = ['backgroundColor']; + * removeStyleProperties(element, propertiesToRemove); + */ + const removeStyleProperties = (element, styleProperties) => { + if (!element?.style) + return; + styleProperties.forEach((styleProperty) => { + element.style.removeProperty(styleProperty); + }); + }; + const isUndefined = (value) => value === undefined; + // Return if the value is a string, double or boolean. + const getTypeFromValue = (value) => { + if (typeof value === 'string') { + return ['char', 'string']; + } + if (typeof value === 'number') { + return ['double', 'double']; + } + if (typeof value === 'boolean') { + return ['boolean', 'boolean']; + } + return ['', '']; + }; + const sdkWindowKey = '_tsEmbedSDK'; + /** + * Stores a value in the global `window` object under the `_tsEmbedSDK` namespace. + * @param key - The key under which the value will be stored. + * @param value - The value to store. + * @param options - Additional options. + * @param options.ignoreIfAlreadyExists - Does not set if value for key is set. + * + * @returns The stored value. + * + * @version SDK: 1.36.2 | ThoughtSpot: * + */ + function storeValueInWindow(key, value, options = {}) { + if (isWindowUndefined()) + return value; + if (!window[sdkWindowKey]) { + window[sdkWindowKey] = {}; + } + if (options.ignoreIfAlreadyExists && key in window[sdkWindowKey]) { + return window[sdkWindowKey][key]; + } + window[sdkWindowKey][key] = value; + return value; + } + /** + * Retrieves a stored value from the global + * `window` object under the `_tsEmbedSDK` namespace. + * Returns undefined in SSR environment. + */ + const getValueFromWindow = (key) => { + if (isWindowUndefined()) + return undefined; + return window?.[sdkWindowKey]?.[key]; + }; + /** + * Check if an array includes a string value + * @param arr - The array to check + * @param key - The string to search for + * @returns boolean indicating if the string is found in the array + */ + const arrayIncludesString = (arr, key) => { + return arr.some(item => typeof item === 'string' && item === key); + }; + /** + * Check if the document is currently in fullscreen mode + */ + const isInFullscreen = () => { + return !!(document.fullscreenElement || + document.webkitFullscreenElement || + document.mozFullScreenElement || + document.msFullscreenElement); + }; + /** + * Handle Present HostEvent by entering fullscreen mode + * @param iframe The iframe element to make fullscreen + */ + const handlePresentEvent = async (iframe) => { + if (isInFullscreen()) { + return; // Already in fullscreen + } + // Browser-specific methods to enter fullscreen mode + const fullscreenMethods = [ + 'requestFullscreen', + 'webkitRequestFullscreen', + 'mozRequestFullScreen', + 'msRequestFullscreen' // IE/Edge + ]; + for (const method of fullscreenMethods) { + if (typeof iframe[method] === 'function') { + try { + const result = iframe[method](); + await Promise.resolve(result); + return; + } + catch (error) { + logger$3.warn(`Failed to enter fullscreen using ${method}:`, error); + } + } + } + logger$3.error('Fullscreen API is not supported by this browser.'); + }; + /** + * Handle ExitPresentMode EmbedEvent by exiting fullscreen mode + */ + const handleExitPresentMode = async () => { + if (!isInFullscreen()) { + return; // Not in fullscreen + } + const exitFullscreenMethods = [ + 'exitFullscreen', + 'webkitExitFullscreen', + 'mozCancelFullScreen', + 'msExitFullscreen' // IE/Edge + ]; + // Try each method until one works + for (const method of exitFullscreenMethods) { + if (typeof document[method] === 'function') { + try { + const result = document[method](); + await Promise.resolve(result); + return; + } + catch (error) { + logger$3.warn(`Failed to exit fullscreen using ${method}:`, error); + } + } + } + logger$3.warn('Exit fullscreen API is not supported by this browser.'); + }; + const calculateVisibleElementData = (element) => { + const rect = element.getBoundingClientRect(); + const windowHeight = window.innerHeight; + const windowWidth = window.innerWidth; + const frameRelativeTop = Math.max(rect.top, 0); + const frameRelativeLeft = Math.max(rect.left, 0); + const frameRelativeBottom = Math.min(windowHeight, rect.bottom); + const frameRelativeRight = Math.min(windowWidth, rect.right); + const data = { + top: Math.max(0, rect.top * -1), + height: Math.max(0, frameRelativeBottom - frameRelativeTop), + left: Math.max(0, rect.left * -1), + width: Math.max(0, frameRelativeRight - frameRelativeLeft), + }; + return data; + }; + /** + * Replaces placeholders in a template string with provided values. + * Placeholders should be in the format {key}. + * @param template - The template string with placeholders + * @param values - An object containing key-value pairs to replace placeholders + * @returns The template string with placeholders replaced + * @example + * formatTemplate('Hello {name}, you are {age} years old', { name: 'John', age: 30 }) + * // Returns: 'Hello John, you are 30 years old' + * + * formatTemplate('Expected {type}, but received {actual}', { type: 'string', actual: 'number' }) + * // Returns: 'Expected string, but received number' + */ + const formatTemplate = (template, values) => { + // This regex /\{(\w+)\}/g finds all placeholders in the format {word} + // and captures the word inside the braces for replacement. + return template.replace(/\{(\w+)\}/g, (match, key) => { + return values[key] !== undefined ? String(values[key]) : match; + }); + }; + const getHostEventsConfig = (viewConfig) => { + return { + shouldBypassPayloadValidation: viewConfig.shouldBypassPayloadValidation, + useHostEventsV2: viewConfig.useHostEventsV2, + }; + }; + /** + * Check if the window is undefined + * If the window is undefined, it means the code is running in a SSR environment. + * @returns true if the window is undefined, false otherwise + * + */ + const isWindowUndefined = () => { + if (typeof window === 'undefined') { + logger$3.error(ERROR_MESSAGE.SSR_ENVIRONMENT_ERROR); + return true; + } + return false; + }; + /** + * Validates that a URL uses only http: or https: protocols. + * Returns a tuple of [isValid, error] so the caller can handle validation errors. + * @param url - The URL string to validate + * @returns [true, null] if valid, [false, Error] if invalid + */ + const validateHttpUrl = (url) => { + try { + const parsedUrl = new URL(url); + if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') { + return [false, new Error(`Invalid protocol: ${parsedUrl.protocol}. Only http: and https: are allowed.`)]; + } + return [true, null]; + } + catch (error) { + return [false, error instanceof Error ? error : new Error(String(error))]; + } + }; + /** + * Sets a query parameter if the value is defined. + * @param queryParams - The query params object to modify + * @param param - The parameter key + * @param value - The value to set + * @param asBoolean - If true, coerces value to boolean + */ + const setParamIfDefined = (queryParams, param, value, asBoolean = false) => { + if (value !== undefined) { + queryParams[param] = asBoolean ? !!value : value; + } + }; + + /** Used for built-in method references. */ + var objectProto$c = Object.prototype; + + /** + * Checks if `value` is likely a prototype object. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a prototype, else `false`. + */ + function isPrototype(value) { + var Ctor = value && value.constructor, + proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto$c; + + return value === proto; + } + + var _isPrototype = isPrototype; + + /** + * Creates a unary function that invokes `func` with its argument transformed. + * + * @private + * @param {Function} func The function to wrap. + * @param {Function} transform The argument transform. + * @returns {Function} Returns the new function. + */ + function overArg(func, transform) { + return function(arg) { + return func(transform(arg)); + }; + } + + var _overArg = overArg; + + /* Built-in method references for those with the same name as other `lodash` methods. */ + var nativeKeys = _overArg(Object.keys, Object); + + var _nativeKeys = nativeKeys; + + /** Used for built-in method references. */ + var objectProto$b = Object.prototype; + + /** Used to check objects for own properties. */ + var hasOwnProperty$a = objectProto$b.hasOwnProperty; + + /** + * The base implementation of `_.keys` which doesn't treat sparse arrays as dense. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + */ + function baseKeys(object) { + if (!_isPrototype(object)) { + return _nativeKeys(object); + } + var result = []; + for (var key in Object(object)) { + if (hasOwnProperty$a.call(object, key) && key != 'constructor') { + result.push(key); + } + } + return result; + } + + var _baseKeys = baseKeys; + + var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; + + function createCommonjsModule(fn) { + var module = { exports: {} }; + return fn(module, module.exports), module.exports; + } + + /** Detect free variable `global` from Node.js. */ + + var freeGlobal = typeof commonjsGlobal == 'object' && commonjsGlobal && commonjsGlobal.Object === Object && commonjsGlobal; + + var _freeGlobal = freeGlobal; + + /** Detect free variable `self`. */ + var freeSelf = typeof self == 'object' && self && self.Object === Object && self; + + /** Used as a reference to the global object. */ + var root = _freeGlobal || freeSelf || Function('return this')(); + + var _root = root; + + /** Built-in value references. */ + var Symbol$1 = _root.Symbol; + + var _Symbol = Symbol$1; + + /** Used for built-in method references. */ + var objectProto$a = Object.prototype; + + /** Used to check objects for own properties. */ + var hasOwnProperty$9 = objectProto$a.hasOwnProperty; + + /** + * Used to resolve the + * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) + * of values. + */ + var nativeObjectToString$1 = objectProto$a.toString; + + /** Built-in value references. */ + var symToStringTag$1 = _Symbol ? _Symbol.toStringTag : undefined; + + /** + * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the raw `toStringTag`. + */ + function getRawTag(value) { + var isOwn = hasOwnProperty$9.call(value, symToStringTag$1), + tag = value[symToStringTag$1]; + + try { + value[symToStringTag$1] = undefined; + var unmasked = true; + } catch (e) {} + + var result = nativeObjectToString$1.call(value); + if (unmasked) { + if (isOwn) { + value[symToStringTag$1] = tag; + } else { + delete value[symToStringTag$1]; + } + } + return result; + } + + var _getRawTag = getRawTag; + + /** Used for built-in method references. */ + var objectProto$9 = Object.prototype; + + /** + * Used to resolve the + * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) + * of values. + */ + var nativeObjectToString = objectProto$9.toString; + + /** + * Converts `value` to a string using `Object.prototype.toString`. + * + * @private + * @param {*} value The value to convert. + * @returns {string} Returns the converted string. + */ + function objectToString(value) { + return nativeObjectToString.call(value); + } + + var _objectToString = objectToString; + + /** `Object#toString` result references. */ + var nullTag$1 = '[object Null]', + undefinedTag = '[object Undefined]'; + + /** Built-in value references. */ + var symToStringTag = _Symbol ? _Symbol.toStringTag : undefined; + + /** + * The base implementation of `getTag` without fallbacks for buggy environments. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the `toStringTag`. + */ + function baseGetTag(value) { + if (value == null) { + return value === undefined ? undefinedTag : nullTag$1; + } + return (symToStringTag && symToStringTag in Object(value)) + ? _getRawTag(value) + : _objectToString(value); + } + + var _baseGetTag = baseGetTag; + + /** + * Checks if `value` is the + * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) + * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(_.noop); + * // => true + * + * _.isObject(null); + * // => false + */ + function isObject(value) { + var type = typeof value; + return value != null && (type == 'object' || type == 'function'); + } + + var isObject_1 = isObject; + + /** `Object#toString` result references. */ + var asyncTag = '[object AsyncFunction]', + funcTag$1 = '[object Function]', + genTag = '[object GeneratorFunction]', + proxyTag = '[object Proxy]'; + + /** + * Checks if `value` is classified as a `Function` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a function, else `false`. + * @example + * + * _.isFunction(_); + * // => true + * + * _.isFunction(/abc/); + * // => false + */ + function isFunction(value) { + if (!isObject_1(value)) { + return false; + } + // The use of `Object#toString` avoids issues with the `typeof` operator + // in Safari 9 which returns 'object' for typed arrays and other constructors. + var tag = _baseGetTag(value); + return tag == funcTag$1 || tag == genTag || tag == asyncTag || tag == proxyTag; + } + + var isFunction_1 = isFunction; + + /** Used to detect overreaching core-js shims. */ + var coreJsData = _root['__core-js_shared__']; + + var _coreJsData = coreJsData; + + /** Used to detect methods masquerading as native. */ + var maskSrcKey = (function() { + var uid = /[^.]+$/.exec(_coreJsData && _coreJsData.keys && _coreJsData.keys.IE_PROTO || ''); + return uid ? ('Symbol(src)_1.' + uid) : ''; + }()); + + /** + * Checks if `func` has its source masked. + * + * @private + * @param {Function} func The function to check. + * @returns {boolean} Returns `true` if `func` is masked, else `false`. + */ + function isMasked(func) { + return !!maskSrcKey && (maskSrcKey in func); + } + + var _isMasked = isMasked; + + /** Used for built-in method references. */ + var funcProto$1 = Function.prototype; + + /** Used to resolve the decompiled source of functions. */ + var funcToString$1 = funcProto$1.toString; + + /** + * Converts `func` to its source code. + * + * @private + * @param {Function} func The function to convert. + * @returns {string} Returns the source code. + */ + function toSource(func) { + if (func != null) { + try { + return funcToString$1.call(func); + } catch (e) {} + try { + return (func + ''); + } catch (e) {} + } + return ''; + } + + var _toSource = toSource; + + /** + * Used to match `RegExp` + * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns). + */ + var reRegExpChar = /[\\^$.*+?()[\]{}|]/g; + + /** Used to detect host constructors (Safari). */ + var reIsHostCtor = /^\[object .+?Constructor\]$/; + + /** Used for built-in method references. */ + var funcProto = Function.prototype, + objectProto$8 = Object.prototype; + + /** Used to resolve the decompiled source of functions. */ + var funcToString = funcProto.toString; + + /** Used to check objects for own properties. */ + var hasOwnProperty$8 = objectProto$8.hasOwnProperty; + + /** Used to detect if a method is native. */ + var reIsNative = RegExp('^' + + funcToString.call(hasOwnProperty$8).replace(reRegExpChar, '\\$&') + .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' + ); + + /** + * The base implementation of `_.isNative` without bad shim checks. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a native function, + * else `false`. + */ + function baseIsNative(value) { + if (!isObject_1(value) || _isMasked(value)) { + return false; + } + var pattern = isFunction_1(value) ? reIsNative : reIsHostCtor; + return pattern.test(_toSource(value)); + } + + var _baseIsNative = baseIsNative; + + /** + * Gets the value at `key` of `object`. + * + * @private + * @param {Object} [object] The object to query. + * @param {string} key The key of the property to get. + * @returns {*} Returns the property value. + */ + function getValue(object, key) { + return object == null ? undefined : object[key]; + } + + var _getValue = getValue; + + /** + * Gets the native function at `key` of `object`. + * + * @private + * @param {Object} object The object to query. + * @param {string} key The key of the method to get. + * @returns {*} Returns the function if it's native, else `undefined`. + */ + function getNative(object, key) { + var value = _getValue(object, key); + return _baseIsNative(value) ? value : undefined; + } + + var _getNative = getNative; + + /* Built-in method references that are verified to be native. */ + var DataView$1 = _getNative(_root, 'DataView'); + + var _DataView = DataView$1; + + /* Built-in method references that are verified to be native. */ + var Map$1 = _getNative(_root, 'Map'); + + var _Map = Map$1; + + /* Built-in method references that are verified to be native. */ + var Promise$1 = _getNative(_root, 'Promise'); + + var _Promise = Promise$1; + + /* Built-in method references that are verified to be native. */ + var Set$1 = _getNative(_root, 'Set'); + + var _Set = Set$1; + + /* Built-in method references that are verified to be native. */ + var WeakMap = _getNative(_root, 'WeakMap'); + + var _WeakMap = WeakMap; + + /** `Object#toString` result references. */ + var mapTag$3 = '[object Map]', + objectTag$2 = '[object Object]', + promiseTag = '[object Promise]', + setTag$3 = '[object Set]', + weakMapTag$1 = '[object WeakMap]'; + + var dataViewTag$2 = '[object DataView]'; + + /** Used to detect maps, sets, and weakmaps. */ + var dataViewCtorString = _toSource(_DataView), + mapCtorString = _toSource(_Map), + promiseCtorString = _toSource(_Promise), + setCtorString = _toSource(_Set), + weakMapCtorString = _toSource(_WeakMap); + + /** + * Gets the `toStringTag` of `value`. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the `toStringTag`. + */ + var getTag = _baseGetTag; + + // Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6. + if ((_DataView && getTag(new _DataView(new ArrayBuffer(1))) != dataViewTag$2) || + (_Map && getTag(new _Map) != mapTag$3) || + (_Promise && getTag(_Promise.resolve()) != promiseTag) || + (_Set && getTag(new _Set) != setTag$3) || + (_WeakMap && getTag(new _WeakMap) != weakMapTag$1)) { + getTag = function(value) { + var result = _baseGetTag(value), + Ctor = result == objectTag$2 ? value.constructor : undefined, + ctorString = Ctor ? _toSource(Ctor) : ''; + + if (ctorString) { + switch (ctorString) { + case dataViewCtorString: return dataViewTag$2; + case mapCtorString: return mapTag$3; + case promiseCtorString: return promiseTag; + case setCtorString: return setTag$3; + case weakMapCtorString: return weakMapTag$1; + } + } + return result; + }; + } + + var _getTag = getTag; + + /** + * Checks if `value` is object-like. A value is object-like if it's not `null` + * and has a `typeof` result of "object". + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + * @example + * + * _.isObjectLike({}); + * // => true + * + * _.isObjectLike([1, 2, 3]); + * // => true + * + * _.isObjectLike(_.noop); + * // => false + * + * _.isObjectLike(null); + * // => false + */ + function isObjectLike(value) { + return value != null && typeof value == 'object'; + } + + var isObjectLike_1 = isObjectLike; + + /** `Object#toString` result references. */ + var argsTag$2 = '[object Arguments]'; + + /** + * The base implementation of `_.isArguments`. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an `arguments` object, + */ + function baseIsArguments(value) { + return isObjectLike_1(value) && _baseGetTag(value) == argsTag$2; + } + + var _baseIsArguments = baseIsArguments; + + /** Used for built-in method references. */ + var objectProto$7 = Object.prototype; + + /** Used to check objects for own properties. */ + var hasOwnProperty$7 = objectProto$7.hasOwnProperty; + + /** Built-in value references. */ + var propertyIsEnumerable$1 = objectProto$7.propertyIsEnumerable; + + /** + * Checks if `value` is likely an `arguments` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an `arguments` object, + * else `false`. + * @example + * + * _.isArguments(function() { return arguments; }()); + * // => true + * + * _.isArguments([1, 2, 3]); + * // => false + */ + var isArguments = _baseIsArguments(function() { return arguments; }()) ? _baseIsArguments : function(value) { + return isObjectLike_1(value) && hasOwnProperty$7.call(value, 'callee') && + !propertyIsEnumerable$1.call(value, 'callee'); + }; + + var isArguments_1 = isArguments; + + /** + * Checks if `value` is classified as an `Array` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array, else `false`. + * @example + * + * _.isArray([1, 2, 3]); + * // => true + * + * _.isArray(document.body.children); + * // => false + * + * _.isArray('abc'); + * // => false + * + * _.isArray(_.noop); + * // => false + */ + var isArray = Array.isArray; + + var isArray_1 = isArray; + + /** Used as references for various `Number` constants. */ + var MAX_SAFE_INTEGER$1 = 9007199254740991; + + /** + * Checks if `value` is a valid array-like length. + * + * **Note:** This method is loosely based on + * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. + * @example + * + * _.isLength(3); + * // => true + * + * _.isLength(Number.MIN_VALUE); + * // => false + * + * _.isLength(Infinity); + * // => false + * + * _.isLength('3'); + * // => false + */ + function isLength(value) { + return typeof value == 'number' && + value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER$1; + } + + var isLength_1 = isLength; + + /** + * Checks if `value` is array-like. A value is considered array-like if it's + * not a function and has a `value.length` that's an integer greater than or + * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is array-like, else `false`. + * @example + * + * _.isArrayLike([1, 2, 3]); + * // => true + * + * _.isArrayLike(document.body.children); + * // => true + * + * _.isArrayLike('abc'); + * // => true + * + * _.isArrayLike(_.noop); + * // => false + */ + function isArrayLike(value) { + return value != null && isLength_1(value.length) && !isFunction_1(value); + } + + var isArrayLike_1 = isArrayLike; + + /** + * This method returns `false`. + * + * @static + * @memberOf _ + * @since 4.13.0 + * @category Util + * @returns {boolean} Returns `false`. + * @example + * + * _.times(2, _.stubFalse); + * // => [false, false] + */ + function stubFalse() { + return false; + } + + var stubFalse_1 = stubFalse; + + var isBuffer_1 = createCommonjsModule(function (module, exports$1) { + /** Detect free variable `exports`. */ + var freeExports = exports$1 && !exports$1.nodeType && exports$1; + + /** Detect free variable `module`. */ + var freeModule = freeExports && 'object' == 'object' && module && !module.nodeType && module; + + /** Detect the popular CommonJS extension `module.exports`. */ + var moduleExports = freeModule && freeModule.exports === freeExports; + + /** Built-in value references. */ + var Buffer = moduleExports ? _root.Buffer : undefined; + + /* Built-in method references for those with the same name as other `lodash` methods. */ + var nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined; + + /** + * Checks if `value` is a buffer. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a buffer, else `false`. + * @example + * + * _.isBuffer(new Buffer(2)); + * // => true + * + * _.isBuffer(new Uint8Array(2)); + * // => false + */ + var isBuffer = nativeIsBuffer || stubFalse_1; + + module.exports = isBuffer; + }); + + /** `Object#toString` result references. */ + var argsTag$1 = '[object Arguments]', + arrayTag$1 = '[object Array]', + boolTag$2 = '[object Boolean]', + dateTag$1 = '[object Date]', + errorTag$1 = '[object Error]', + funcTag = '[object Function]', + mapTag$2 = '[object Map]', + numberTag$1 = '[object Number]', + objectTag$1 = '[object Object]', + regexpTag$1 = '[object RegExp]', + setTag$2 = '[object Set]', + stringTag$1 = '[object String]', + weakMapTag = '[object WeakMap]'; + + var arrayBufferTag$1 = '[object ArrayBuffer]', + dataViewTag$1 = '[object DataView]', + float32Tag = '[object Float32Array]', + float64Tag = '[object Float64Array]', + int8Tag = '[object Int8Array]', + int16Tag = '[object Int16Array]', + int32Tag = '[object Int32Array]', + uint8Tag = '[object Uint8Array]', + uint8ClampedTag = '[object Uint8ClampedArray]', + uint16Tag = '[object Uint16Array]', + uint32Tag = '[object Uint32Array]'; + + /** Used to identify `toStringTag` values of typed arrays. */ + var typedArrayTags = {}; + typedArrayTags[float32Tag] = typedArrayTags[float64Tag] = + typedArrayTags[int8Tag] = typedArrayTags[int16Tag] = + typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] = + typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] = + typedArrayTags[uint32Tag] = true; + typedArrayTags[argsTag$1] = typedArrayTags[arrayTag$1] = + typedArrayTags[arrayBufferTag$1] = typedArrayTags[boolTag$2] = + typedArrayTags[dataViewTag$1] = typedArrayTags[dateTag$1] = + typedArrayTags[errorTag$1] = typedArrayTags[funcTag] = + typedArrayTags[mapTag$2] = typedArrayTags[numberTag$1] = + typedArrayTags[objectTag$1] = typedArrayTags[regexpTag$1] = + typedArrayTags[setTag$2] = typedArrayTags[stringTag$1] = + typedArrayTags[weakMapTag] = false; + + /** + * The base implementation of `_.isTypedArray` without Node.js optimizations. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. + */ + function baseIsTypedArray(value) { + return isObjectLike_1(value) && + isLength_1(value.length) && !!typedArrayTags[_baseGetTag(value)]; + } + + var _baseIsTypedArray = baseIsTypedArray; + + /** + * The base implementation of `_.unary` without support for storing metadata. + * + * @private + * @param {Function} func The function to cap arguments for. + * @returns {Function} Returns the new capped function. + */ + function baseUnary(func) { + return function(value) { + return func(value); + }; + } + + var _baseUnary = baseUnary; + + var _nodeUtil = createCommonjsModule(function (module, exports$1) { + /** Detect free variable `exports`. */ + var freeExports = exports$1 && !exports$1.nodeType && exports$1; + + /** Detect free variable `module`. */ + var freeModule = freeExports && 'object' == 'object' && module && !module.nodeType && module; + + /** Detect the popular CommonJS extension `module.exports`. */ + var moduleExports = freeModule && freeModule.exports === freeExports; + + /** Detect free variable `process` from Node.js. */ + var freeProcess = moduleExports && _freeGlobal.process; + + /** Used to access faster Node.js helpers. */ + var nodeUtil = (function() { + try { + // Use `util.types` for Node.js 10+. + var types = freeModule && freeModule.require && freeModule.require('util').types; + + if (types) { + return types; + } + + // Legacy `process.binding('util')` for Node.js < 10. + return freeProcess && freeProcess.binding && freeProcess.binding('util'); + } catch (e) {} + }()); + + module.exports = nodeUtil; + }); + + /* Node.js helper references. */ + var nodeIsTypedArray = _nodeUtil && _nodeUtil.isTypedArray; + + /** + * Checks if `value` is classified as a typed array. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. + * @example + * + * _.isTypedArray(new Uint8Array); + * // => true + * + * _.isTypedArray([]); + * // => false + */ + var isTypedArray = nodeIsTypedArray ? _baseUnary(nodeIsTypedArray) : _baseIsTypedArray; + + var isTypedArray_1 = isTypedArray; + + /** `Object#toString` result references. */ + var mapTag$1 = '[object Map]', + setTag$1 = '[object Set]'; + + /** Used for built-in method references. */ + var objectProto$6 = Object.prototype; + + /** Used to check objects for own properties. */ + var hasOwnProperty$6 = objectProto$6.hasOwnProperty; + + /** + * Checks if `value` is an empty object, collection, map, or set. + * + * Objects are considered empty if they have no own enumerable string keyed + * properties. + * + * Array-like values such as `arguments` objects, arrays, buffers, strings, or + * jQuery-like collections are considered empty if they have a `length` of `0`. + * Similarly, maps and sets are considered empty if they have a `size` of `0`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is empty, else `false`. + * @example + * + * _.isEmpty(null); + * // => true + * + * _.isEmpty(true); + * // => true + * + * _.isEmpty(1); + * // => true + * + * _.isEmpty([1, 2, 3]); + * // => false + * + * _.isEmpty({ 'a': 1 }); + * // => false + */ + function isEmpty$1(value) { + if (value == null) { + return true; + } + if (isArrayLike_1(value) && + (isArray_1(value) || typeof value == 'string' || typeof value.splice == 'function' || + isBuffer_1(value) || isTypedArray_1(value) || isArguments_1(value))) { + return !value.length; + } + var tag = _getTag(value); + if (tag == mapTag$1 || tag == setTag$1) { + return !value.size; + } + if (_isPrototype(value)) { + return !_baseKeys(value).length; + } + for (var key in value) { + if (hasOwnProperty$6.call(value, key)) { + return false; + } + } + return true; + } + + var isEmpty_1 = isEmpty$1; + + var UIPassthroughEvent; + (function (UIPassthroughEvent) { + UIPassthroughEvent["PinAnswerToLiveboard"] = "addVizToPinboard"; + UIPassthroughEvent["SaveAnswer"] = "saveAnswer"; + UIPassthroughEvent["GetDiscoverabilityStatus"] = "getDiscoverabilityStatus"; + UIPassthroughEvent["GetAvailableUIPassthroughs"] = "getAvailableUiPassthroughs"; + UIPassthroughEvent["GetAnswerConfig"] = "getAnswerPageConfig"; + UIPassthroughEvent["GetLiveboardConfig"] = "getPinboardPageConfig"; + UIPassthroughEvent["GetUnsavedAnswerTML"] = "getUnsavedAnswerTML"; + UIPassthroughEvent["UpdateFilters"] = "updateFilters"; + UIPassthroughEvent["Drilldown"] = "drillDown"; + UIPassthroughEvent["GetAnswerSession"] = "getAnswerSession"; + UIPassthroughEvent["GetFilters"] = "getFilters"; + UIPassthroughEvent["GetIframeUrl"] = "getIframeUrl"; + UIPassthroughEvent["GetParameters"] = "getParameters"; + UIPassthroughEvent["GetTML"] = "getTML"; + UIPassthroughEvent["GetTabs"] = "getTabs"; + UIPassthroughEvent["GetExportRequestForCurrentPinboard"] = "getExportRequestForCurrentPinboard"; + })(UIPassthroughEvent || (UIPassthroughEvent = {})); + + const EndPoints = { + AUTH_VERIFICATION: '/callosum/v1/session/info', + SESSION_INFO: '/callosum/v1/session/info', + PREAUTH_INFO: '/prism/preauth/info', + SAML_LOGIN_TEMPLATE: (targetUrl) => `/callosum/v1/saml/login?targetURLPath=${targetUrl}`, + OIDC_LOGIN_TEMPLATE: (targetUrl) => `/callosum/v1/oidc/login?targetURLPath=${targetUrl}`, + TOKEN_LOGIN: '/callosum/v1/session/login/token', + BASIC_LOGIN: '/callosum/v1/session/login', + LOGOUT: '/callosum/v1/session/logout', + EXECUTE_TML: '/api/rest/2.0/metadata/tml/import', + EXPORT_TML: '/api/rest/2.0/metadata/tml/export', + IS_ACTIVE: '/callosum/v1/session/isactive', + }; + /** + * + * @param url + * @param options + */ + function failureLoggedFetch(url, options = {}) { + return fetch(url, options).then(async (r) => { + if (!r.ok && r.type !== 'opaqueredirect' && r.type !== 'opaque') { + logger$3.error('Failure', await r.text?.()); + } + return r; + }); + } + /** + * Service to validate a auth token against a ThoughtSpot host. + * @param thoughtSpotHost : ThoughtSpot host to verify the token against. + * @param authToken : Auth token to verify. + */ + async function verifyTokenService(thoughtSpotHost, authToken) { + const authVerificationUrl = `${thoughtSpotHost}${EndPoints.IS_ACTIVE}`; + try { + const res = await fetch(authVerificationUrl, { + headers: { + Authorization: `Bearer ${authToken}`, + 'x-requested-by': 'ThoughtSpot', + }, + credentials: 'omit', + }); + return res.ok; + } + catch (e) { + logger$3.warn(`Token Verification Service failed : ${e.message}`); + } + return false; + } + /** + * + * @param authEndpoint + */ + async function fetchAuthTokenService(authEndpoint) { + return fetch(authEndpoint); + } + /** + * + * @param thoughtSpotHost + * @param username + * @param authToken + */ + async function fetchAuthService(thoughtSpotHost, username, authToken) { + const fetchUrlParams = username + ? `username=${username}&auth_token=${authToken}` + : `auth_token=${authToken}`; + return failureLoggedFetch(`${thoughtSpotHost}${EndPoints.TOKEN_LOGIN}?${fetchUrlParams}`, { + credentials: 'include', + // We do not want to follow the redirect, as it starts giving a CORS + // error + redirect: 'manual', + }); + } + /** + * + * @param thoughtSpotHost + * @param username + * @param authToken + */ + async function fetchAuthPostService(thoughtSpotHost, username, authToken) { + const bodyPrams = username + ? `username=${encodeURIComponent(username)}&auth_token=${encodeURIComponent(authToken)}` + : `auth_token=${encodeURIComponent(authToken)}`; + return failureLoggedFetch(`${thoughtSpotHost}${EndPoints.TOKEN_LOGIN}`, { + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'x-requested-by': 'ThoughtSpot', + }, + body: bodyPrams, + credentials: 'include', + // We do not want to follow the redirect, as it starts giving a CORS + // error + redirect: 'manual', + }); + } + /** + * + * @param thoughtSpotHost + * @param username + * @param password + */ + async function fetchBasicAuthService(thoughtSpotHost, username, password) { + return failureLoggedFetch(`${thoughtSpotHost}${EndPoints.BASIC_LOGIN}`, { + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'x-requested-by': 'ThoughtSpot', + }, + body: `username=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}`, + credentials: 'include', + }); + } + + const cacheAuthTokenKey = 'cachedAuthToken'; + const getCacheAuthToken = () => getValueFromWindow(cacheAuthTokenKey); + const storeAuthTokenInCache = (token) => { + storeValueInWindow(cacheAuthTokenKey, token); + }; + // This method can be used to get the authToken using the embedConfig + /** + * + * @param embedConfig + */ + async function getAuthenticationToken(embedConfig, skipvalidation = false) { + const cachedAuthToken = getCacheAuthToken(); + // Since we don't have token validation enabled , we cannot tell if the + // cached token is valid or not. So we will always fetch a new token. + if (cachedAuthToken && !embedConfig.disableTokenVerification && !skipvalidation) { + let isCachedTokenStillValid; + try { + isCachedTokenStillValid = await validateAuthToken(embedConfig, cachedAuthToken, true); + } + catch { + isCachedTokenStillValid = false; + } + if (isCachedTokenStillValid) + return cachedAuthToken; + } + const { authEndpoint, getAuthToken } = embedConfig; + let authToken = null; + if (getAuthToken) { + authToken = await getAuthToken(); + } + else { + const response = await fetchAuthTokenService(authEndpoint); + authToken = await response.text(); + } + try { + // this will throw error if the token is not valid + await validateAuthToken(embedConfig, authToken); + } + catch (e) { + logger$3.error(`${ERROR_MESSAGE.INVALID_TOKEN_ERROR} Error : ${e.message}`); + throw e; + } + storeAuthTokenInCache(authToken); + return authToken; + } + const validateAuthToken = async (embedConfig, authToken, suppressAlert) => { + // even if token verification is disabled, we will still validate + // that the token is a string before proceeding. + if (typeof authToken !== 'string') { + const errorMessage = formatTemplate(ERROR_MESSAGE.INVALID_TOKEN_TYPE_ERROR, { + invalidType: typeof authToken, + }); + logger$3.error(errorMessage); + throw new Error(errorMessage); + } + const cachedAuthToken = getCacheAuthToken(); + if (embedConfig.disableTokenVerification) { + logger$3.info('Token verification is disabled. Assuming token is valid.'); + return true; + } + try { + const isTokenValid = await verifyTokenService(embedConfig.thoughtSpotHost, authToken); + if (isTokenValid) + return true; + } + catch { + return false; + } + if (cachedAuthToken && cachedAuthToken === authToken) { + if (!embedConfig.suppressErrorAlerts && !suppressAlert) { + alert(ERROR_MESSAGE.DUPLICATE_TOKEN_ERR); + } + throw new Error(ERROR_MESSAGE.DUPLICATE_TOKEN_ERR); + } + else { + throw new Error(ERROR_MESSAGE.INVALID_TOKEN_ERROR); + } + }; + /** + * Resets the auth token and a new token will be fetched on the next request. + * @example + * ```js + * resetCachedAuthToken(); + * ``` + * @version SDK: 1.28.0 | ThoughtSpot: * + * @group Authentication / Init + */ + const resetCachedAuthToken = () => { + storeAuthTokenInCache(null); + }; + + const configKey = 'embedConfig'; + /** + * Gets the embed configuration settings that were used to + * initialize the SDK. + * @returns {@link EmbedConfig} + * + * @example + * ```js + * import { getInitConfig } from '@thoughtspot/visual-embed-sdk'; + * // Call the getInitConfig method to retrieve the embed configuration + * const config = getInitConfig(); + * // Log the configuration settings + * console.log(config); + * ``` + * Returns the link:https://developers.thoughtspot.com/docs/Interface_EmbedConfig[EmbedConfig] + * object, which contains the configuration settings used to + * initialize the SDK, including the following: + * + * - `thoughtSpotHost` - ThoughtSpot host URL + * - `authType`: The authentication method used. For example, + * `AuthServerCookieless` for `AuthType.TrustedAuthTokenCookieless` + * - `customizations` - Style, text, and icon customization settings + * that were applied during the SDK initialization. + * + * The following JSON output shows the embed configuration + * settings returned from the code in the previous example: + * + * @example + * ```json + * { + * "thoughtSpotHost": "https://{ThoughtSpot-Host}", + * "authType": "AuthServerCookieless", + * "customizations": { + * "style": { + * "customCSS": { + * "variables": { + * "--ts-var-button--secondary-background": "#7492d5", + * "--ts-var-button--secondary--hovers-background": "#aac2f8", + * "--ts-var-root-background": "#f1f4f8" + * } + * } + * } + * }, + * "loginFailedMessage": "Login failed, please try again", + * "authTriggerText": "Authorize", + * "disableTokenVerification": true, + * "authTriggerContainer": "#your-own-div" + * } + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw, and later + * @group Global methods + */ + const getEmbedConfig = () => getValueFromWindow(configKey) || {}; + /** + * Sets the configuration embed was initialized with. + * And returns the new configuration. + * @param newConfig The configuration to set. + * @version SDK: 1.27.0 | ThoughtSpot: 9.8.0.cl, 9.8.1.sw, and later + * @group Global methods + */ + const setEmbedConfig = (newConfig) => { + storeValueInWindow(configKey, newConfig); + return getValueFromWindow(configKey); + }; + + /** + * Fetch wrapper that adds the authentication token to the request. + * Use this to call the ThoughtSpot APIs when using the visual embed sdk. + * The interface for this method is the same as Web `Fetch`. + * @param input + * @param init + * @example + * ```js + * tokenizedFetch("/api/rest/2.0/auth/session/user", { + * // .. fetch options .. + * }); + * ``` + * @version SDK: 1.28.0 + * @group Global methods + */ + const tokenizedFetch = async (input, init) => { + const embedConfig = getEmbedConfig(); + const options = { ...init }; + let token; + if (embedConfig.authType !== AuthType.TrustedAuthTokenCookieless) { + token = getCacheAuthToken(); + if (!token) { + return fetch(input, { ...options, credentials: 'include' }); + } + } + else { + token = await getAuthenticationToken(embedConfig); + } + const req = new Request(input, options); + if (token) { + req.headers.append('Authorization', `Bearer ${token}`); + } + return fetch(req); + }; + + /** + * + * @param root0 + * @param root0.query + * @param root0.variables + * @param root0.thoughtSpotHost + * @param root0.isCompositeQuery + */ + async function graphqlQuery({ query, variables, thoughtSpotHost, isCompositeQuery = false, }) { + const operationName = getOperationNameFromQuery(query); + try { + const response = await tokenizedFetch(`${thoughtSpotHost}/prism/?op=${operationName}`, { + method: 'POST', + headers: { + 'content-type': 'application/json;charset=UTF-8', + 'x-requested-by': 'ThoughtSpot', + accept: '*/*', + 'accept-language': 'en-us', + }, + body: JSON.stringify({ + operationName, + query, + variables, + }), + credentials: 'include', + }); + const result = await response.json(); + const dataValues = Object.values(result.data); + return (isCompositeQuery) ? result.data : dataValues[0]; + } + catch (error) { + return error; + } + } + + const getSourceDetailQuery = ` + query GetSourceDetail($ids: [GUID!]!) { + getSourceDetailById(ids: $ids, type: LOGICAL_TABLE) { + id + name + description + authorName + authorDisplayName + isExternal + type + created + modified + columns { + id + name + author + authorDisplayName + description + dataType + type + modified + ownerName + owner + dataRecency + sources { + tableId + tableName + columnId + columnName + __typename + } + synonyms + cohortAnswerId + __typename + } + relationships + destinationRelationships + dataSourceId + __typename + } + } +`; + const sourceDetailCache = new Map(); + /** + * + * @param thoughtSpotHost + * @param sourceId + */ + async function getSourceDetail(thoughtSpotHost, sourceId) { + if (sourceDetailCache.get(sourceId)) { + return sourceDetailCache.get(sourceId); + } + const details = await graphqlQuery({ + query: getSourceDetailQuery, + variables: { + ids: [sourceId], + }, + thoughtSpotHost, + }); + const souceDetails = details[0]; + if (souceDetails) { + sourceDetailCache.set(sourceId, souceDetails); + } + return souceDetails; + } + + const bachSessionId = ` +id { + sessionId + genNo + acSession { + sessionId + genNo + } +} +`; + const getUnaggregatedAnswerSession = ` +mutation GetUnAggregatedAnswerSession($session: BachSessionIdInput!, $columns: [UserPointSelectionInput!]!) { + Answer__getUnaggregatedAnswer(session: $session, columns: $columns) { + ${bachSessionId} + answer { + visualizations { + ... on TableViz { + columns { + column { + id + name + referencedColumns { + guid + displayName + } + } + } + } + } + } + } +} +`; + const removeColumns = ` +mutation RemoveColumns($session: BachSessionIdInput!, $logicalColumnIds: [GUID!], $columnIds: [GUID!]) { + Answer__removeColumns( + session: $session + logicalColumnIds: $logicalColumnIds + columnIds: $columnIds + ) { + ${bachSessionId} + } +} + `; + const addColumns = ` + mutation AddColumns($session: BachSessionIdInput!, $columns: [AnswerColumnInfo!]!) { + Answer__addColumn(session: $session, columns: $columns) { + ${bachSessionId} + } + } + `; + const addFilter = ` + mutation AddUpdateFilter($session: BachSessionIdInput!, $params: AddUpdateFilterInput!) { + Answer__addUpdateFilter(session: $session, params: $params) { + ${bachSessionId} + } + } +`; + const getAnswer = ` + query GetAnswer($session: BachSessionIdInput!) { + getAnswer(session: $session) { + ${bachSessionId} + answer { + id + name + description + displayMode + sources { + header { + guid + displayName + } + } + filterGroups { + columnInfo { + name + referencedColumns { + guid + displayName + } + } + filters { + filterContent { + filterType + negate + value { + key + } + } + } + } + metadata { + author + authorId + createdAt + isDiscoverable + isHidden + modifiedAt + } + visualizations { + ... on TableViz { + id + columns { + column { + id + name + referencedColumns { + guid + displayName + } + } + } + } + ... on ChartViz { + id + } + } + } + } + } + +`; + const getAnswerData = ` + query GetTableWithHeadlineData($session: BachSessionIdInput!, $deadline: Int!, $dataPaginationParams: DataPaginationParamsInput!) { + getAnswer(session: $session) { + ${bachSessionId} + answer { + id + visualizations { + id + ... on TableViz { + columns { + column { + id + name + type + aggregationType + dataType + } + } + data(deadline: $deadline, pagination: $dataPaginationParams) + } + } + } + } + } +`; + const addVizToLiveboard = ` + mutation AddVizToLiveboard(liveboardId: GUID!, session: BachSessionIdInput!, tabId: GUID, vizId: GUID!) { + Answer__addVizToPinboard( + pinboardId: liveboardId + + session: $session + + tabId: $tabId + + vizId: $vizId + ) { + ${bachSessionId} + } + } +`; + const getSQLQuery = ` + mutation GetSQLQuery($session: BachSessionIdInput!) { + Answer__getQuery(session: $session) { + sql + } + } +`; + const updateDisplayMode = ` + mutation UpdateDisplayMode( + $session: BachSessionIdInput! + $displayMode: DisplayMode +) { + Answer__updateProperties(session: $session, displayMode: $displayMode) { + id { + sessionId + genNo + acSession { + sessionId + genNo + } + } + answer { + id + displayMode + suggestedDisplayMode + } + } +} +`; + const getAnswerTML = ` +mutation GetUnsavedAnswerTML($session: BachSessionIdInput!, $exportDependencies: Boolean, $formatType: EDocFormatType, $exportPermissions: Boolean, $exportFqn: Boolean) { + UnsavedAnswer_getTML( + session: $session + exportDependencies: $exportDependencies + formatType: $formatType + exportPermissions: $exportPermissions + exportFqn: $exportFqn + ) { + zipFile + object { + edoc + name + type + __typename + } + __typename + } +}`; + + // import YAML from 'yaml'; + var OperationType; + (function (OperationType) { + OperationType["GetChartWithData"] = "GetChartWithData"; + OperationType["GetTableWithHeadlineData"] = "GetTableWithHeadlineData"; + })(OperationType || (OperationType = {})); + const DATA_TYPES = ['DATE', 'DATE_TIME', 'TIME']; + /** + * AnswerService provides a simple way to work with ThoughtSpot Answers. + * + * This service allows you to interact with ThoughtSpot Answers programmatically, + * making it easy to customize visualizations, filter data, and extract insights + * directly from your application. + * + * You can use this service to: + * + * - Add or remove columns from Answers (`addColumns`, `removeColumns`, + * `addColumnsByName`) + * - Apply filters to Answers (`addFilter`) + * - Get data from Answers in different formats (JSON, CSV, PNG) (`fetchData`, + * `fetchCSVBlob`, `fetchPNGBlob`) + * - Get data for specific points in visualizations + * (`getUnderlyingDataForPoint`) + * - Run custom queries (`executeQuery`) + * - Add visualizations to Liveboards (`addDisplayedVizToLiveboard`) + * + * @example + * ```js + * // Get the answer service + * embed.on(EmbedEvent.Data, async (e) => { + * const service = await embed.getAnswerService(); + * + * // Add columns to the answer + * await service.addColumnsByName(["Sales", "Region"]); + * + * // Get the data + * const data = await service.fetchData(); + * console.log(data); + * }); + * ``` + * + * @example + * ```js + * // Get data for a point in a visualization + * embed.on(EmbedEvent.CustomAction, async (e) => { + * const underlying = await e.answerService.getUnderlyingDataForPoint([ + * 'Product Name', + * 'Sales Amount' + * ]); + * + * const data = await underlying.fetchData(0, 100); + * console.log(data); + * }); + * ``` + * + * @version SDK: 1.25.0 | ThoughtSpot: 9.10.0.cl + * @group Events + */ + class AnswerService { + /** + * Should not need to be called directly. + * @param session + * @param answer + * @param thoughtSpotHost + * @param selectedPoints + */ + constructor(session, answer, thoughtSpotHost, selectedPoints) { + this.session = session; + this.thoughtSpotHost = thoughtSpotHost; + this.selectedPoints = selectedPoints; + this.tmlOverride = {}; + this.session = removeTypename(session); + this.answer = answer; + } + /** + * Get the details about the source used in the answer. + * This can be used to get the list of all columns in the data source for example. + */ + async getSourceDetail() { + const sourceId = (await this.getAnswer()).sources[0].header.guid; + return getSourceDetail(this.thoughtSpotHost, sourceId); + } + /** + * Remove columnIds and return updated answer session. + * @param columnIds + * @returns + */ + async removeColumns(columnIds) { + return this.executeQuery(removeColumns, { + logicalColumnIds: columnIds, + }); + } + /** + * Add columnIds and return updated answer session. + * @param columnIds + * @returns + */ + async addColumns(columnIds) { + return this.executeQuery(addColumns, { + columns: columnIds.map((colId) => ({ logicalColumnId: colId })), + }); + } + /** + * Add columns by names and return updated answer session. + * @param columnNames + * @returns + * @example + * ```js + * embed.on(EmbedEvent.Data, async (e) => { + * const service = await embed.getAnswerService(); + * await service.addColumnsByName([ + * "col name 1", + * "col name 2" + * ]); + * console.log(await service.fetchData()); + * }); + * ``` + */ + async addColumnsByName(columnNames) { + const sourceDetail = await this.getSourceDetail(); + const columnGuids = getGuidsFromColumnNames(sourceDetail, columnNames); + return this.addColumns([...columnGuids]); + } + /** + * Add a filter to the answer. + * @param columnName + * @param operator + * @param values + * @returns + */ + async addFilter(columnName, operator, values) { + const sourceDetail = await this.getSourceDetail(); + const columnGuids = getGuidsFromColumnNames(sourceDetail, [columnName]); + return this.executeQuery(addFilter, { + params: { + filterContent: [{ + filterType: operator, + value: values.map((v) => { + const [type, prefix] = getTypeFromValue(v); + return { + type: type.toUpperCase(), + [`${prefix}Value`]: v, + }; + }), + }], + filterGroupId: { + logicalColumnId: columnGuids.values().next().value, + }, + }, + }); + } + async updateDisplayMode(displayMode = "TABLE_MODE") { + return this.executeQuery(updateDisplayMode, { + displayMode, + }); + } + async getSQLQuery(fetchSQLWithAllColumns = false) { + if (fetchSQLWithAllColumns) { + await this.updateDisplayMode("TABLE_MODE"); + } + const { sql } = await this.executeQuery(getSQLQuery, {}); + return sql; + } + /** + * Fetch data from the answer. + * @param offset + * @param size + * @returns + */ + async fetchData(offset = 0, size = 1000) { + const { answer } = await this.executeQuery(getAnswerData, { + deadline: 0, + dataPaginationParams: { + isClientPaginated: true, + offset, + size, + }, + }); + const { columns, data } = answer.visualizations.find((viz) => !!viz.data) || {}; + return { + columns, + data, + }; + } + /** + * Fetch the data for the answer as a CSV blob. This might be + * quicker for larger data. + * @param userLocale + * @param includeInfo Include the CSV header in the output + * @returns Response + */ + async fetchCSVBlob(userLocale = 'en-us', includeInfo = false) { + const fetchUrl = this.getFetchCSVBlobUrl(userLocale, includeInfo); + return tokenizedFetch(fetchUrl, { + credentials: 'include', + }); + } + /** + * Fetch the data for the answer as a PNG blob. This might be + * quicker for larger data. + * @param userLocale + * @param includeInfo + * @param omitBackground Omit the background in the PNG + * @param deviceScaleFactor The scale factor for the PNG + * @return Response + */ + async fetchPNGBlob(userLocale = 'en-us', omitBackground = false, deviceScaleFactor = 2) { + const fetchUrl = this.getFetchPNGBlobUrl(userLocale, omitBackground, deviceScaleFactor); + return tokenizedFetch(fetchUrl, { + credentials: 'include', + }); + } + /** + * Just get the internal URL for this answer's data + * as a CSV blob. + * @param userLocale + * @param includeInfo + * @returns + */ + getFetchCSVBlobUrl(userLocale = 'en-us', includeInfo = false) { + return `${this.thoughtSpotHost}/prism/download/answer/csv?sessionId=${this.session.sessionId}&genNo=${this.session.genNo}&userLocale=${userLocale}&exportFileName=data&hideCsvHeader=${!includeInfo}`; + } + /** + * Just get the internal URL for this answer's data + * as a PNG blob. + * @param userLocale + * @param omitBackground + * @param deviceScaleFactor + */ + getFetchPNGBlobUrl(userLocale = 'en-us', omitBackground = false, deviceScaleFactor = 2) { + return `${this.thoughtSpotHost}/prism/download/answer/png?sessionId=${this.session.sessionId}&deviceScaleFactor=${deviceScaleFactor}&omitBackground=${omitBackground}&genNo=${this.session.genNo}&userLocale=${userLocale}&exportFileName=data`; + } + /** + * Get underlying data given a point and the output column names. + * In case of a context menu action, the selectedPoints are + * automatically passed. + * @param outputColumnNames + * @param selectedPoints + * @example + * ```js + * embed.on(EmbedEvent.CustomAction, e => { + * const underlying = await e.answerService.getUnderlyingDataForPoint([ + * 'col name 1' // The column should exist in the data source. + * ]); + * const data = await underlying.fetchData(0, 100); + * }) + * ``` + * @version SDK: 1.25.0 | ThoughtSpot: 9.10.0.cl + */ + async getUnderlyingDataForPoint(outputColumnNames, selectedPoints) { + if (!selectedPoints && !this.selectedPoints) { + throw new Error('Needs to be triggered in context of a point'); + } + if (!selectedPoints) { + selectedPoints = getSelectedPointsForUnderlyingDataQuery(this.selectedPoints); + } + const sourceDetail = await this.getSourceDetail(); + const ouputColumnGuids = getGuidsFromColumnNames(sourceDetail, outputColumnNames); + const unAggAnswer = await graphqlQuery({ + query: getUnaggregatedAnswerSession, + variables: { + session: this.session, + columns: selectedPoints, + }, + thoughtSpotHost: this.thoughtSpotHost, + }); + const unaggAnswerSession = new AnswerService(unAggAnswer.id, unAggAnswer.answer, this.thoughtSpotHost); + const currentColumns = new Set(unAggAnswer.answer.visualizations[0].columns + .map((c) => c.column.referencedColumns[0].guid)); + const columnsToAdd = [...ouputColumnGuids].filter((col) => !currentColumns.has(col)); + if (columnsToAdd.length) { + await unaggAnswerSession.addColumns(columnsToAdd); + } + const columnsToRemove = [...currentColumns].filter((col) => !ouputColumnGuids.has(col)); + if (columnsToRemove.length) { + await unaggAnswerSession.removeColumns(columnsToRemove); + } + return unaggAnswerSession; + } + /** + * Execute a custom graphql query in the context of the answer. + * @param query graphql query + * @param variables graphql variables + * @returns + */ + async executeQuery(query, variables) { + const data = await graphqlQuery({ + query, + variables: { + session: this.session, + ...variables, + }, + thoughtSpotHost: this.thoughtSpotHost, + isCompositeQuery: false, + }); + this.session = deepMerge(this.session, data?.id || {}); + return data; + } + /** + * Get the internal session details for the answer. + * @returns + */ + getSession() { + return this.session; + } + async getAnswer() { + if (this.answer) { + return this.answer; + } + this.answer = this.executeQuery(getAnswer, {}).then((data) => data?.answer); + return this.answer; + } + async getTML() { + const { object } = await this.executeQuery(getAnswerTML, {}); + const edoc = object[0].edoc; + const YAML = await Promise.resolve().then(function () { return index; }); + const parsedDoc = YAML.parse(edoc); + return { + answer: { + ...parsedDoc.answer, + ...this.tmlOverride, + }, + }; + } + async addDisplayedVizToLiveboard(liveboardId) { + const { displayMode, visualizations } = await this.getAnswer(); + const viz = getDisplayedViz(visualizations, displayMode); + return this.executeQuery(addVizToLiveboard, { + liveboardId, + vizId: viz.id, + }); + } + setTMLOverride(override) { + this.tmlOverride = override; + } + } + /** + * + * @param sourceDetail + * @param colNames + */ + function getGuidsFromColumnNames(sourceDetail, colNames) { + const cols = sourceDetail.columns.reduce((colSet, col) => { + colSet[col.name.toLowerCase()] = col; + return colSet; + }, {}); + return new Set(colNames.map((colName) => { + const col = cols[colName.toLowerCase()]; + return col.id; + })); + } + /** + * + * @param selectedPoints + */ + function getSelectedPointsForUnderlyingDataQuery(selectedPoints) { + const underlyingDataPoint = []; + /** + * + * @param colVal + */ + function addPointFromColVal(colVal) { + const dataType = colVal.column.dataType; + colVal.column.id; + let dataValue; + if (DATA_TYPES.includes(dataType)) { + if (Number.isFinite(colVal.value)) { + dataValue = [{ + epochRange: { + startEpoch: colVal.value, + }, + }]; + // Case for custom calendar. + } + else if (colVal.value?.v) { + dataValue = [{ + epochRange: { + startEpoch: colVal.value.v.s, + endEpoch: colVal.value.v.e, + }, + }]; + } + } + else { + dataValue = [{ value: colVal.value }]; + } + underlyingDataPoint.push({ + columnId: colVal.column.id, + dataValue, + }); + } + selectedPoints.forEach((p) => { + p.selectedAttributes.forEach(addPointFromColVal); + }); + return underlyingDataPoint; + } + /** + * + * @param visualizations + * @param displayMode + */ + function getDisplayedViz(visualizations, displayMode) { + if (displayMode === 'CHART_MODE') { + return visualizations.find((viz) => viz.__typename === 'ChartViz'); + } + return visualizations.find((viz) => viz.__typename === 'TableViz'); + } + + /** + * Appends the elements of `values` to `array`. + * + * @private + * @param {Array} array The array to modify. + * @param {Array} values The values to append. + * @returns {Array} Returns `array`. + */ + function arrayPush(array, values) { + var index = -1, + length = values.length, + offset = array.length; + + while (++index < length) { + array[offset + index] = values[index]; + } + return array; + } + + var _arrayPush = arrayPush; + + /** Built-in value references. */ + var spreadableSymbol = _Symbol ? _Symbol.isConcatSpreadable : undefined; + + /** + * Checks if `value` is a flattenable `arguments` object or array. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is flattenable, else `false`. + */ + function isFlattenable(value) { + return isArray_1(value) || isArguments_1(value) || + !!(spreadableSymbol && value && value[spreadableSymbol]); + } + + var _isFlattenable = isFlattenable; + + /** + * The base implementation of `_.flatten` with support for restricting flattening. + * + * @private + * @param {Array} array The array to flatten. + * @param {number} depth The maximum recursion depth. + * @param {boolean} [predicate=isFlattenable] The function invoked per iteration. + * @param {boolean} [isStrict] Restrict to values that pass `predicate` checks. + * @param {Array} [result=[]] The initial result value. + * @returns {Array} Returns the new flattened array. + */ + function baseFlatten(array, depth, predicate, isStrict, result) { + var index = -1, + length = array.length; + + predicate || (predicate = _isFlattenable); + result || (result = []); + + while (++index < length) { + var value = array[index]; + if (depth > 0 && predicate(value)) { + if (depth > 1) { + // Recursively flatten arrays (susceptible to call stack limits). + baseFlatten(value, depth - 1, predicate, isStrict, result); + } else { + _arrayPush(result, value); + } + } else if (!isStrict) { + result[result.length] = value; + } + } + return result; + } + + var _baseFlatten = baseFlatten; + + /** + * A specialized version of `_.map` for arrays without support for iteratee + * shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the new mapped array. + */ + function arrayMap(array, iteratee) { + var index = -1, + length = array == null ? 0 : array.length, + result = Array(length); + + while (++index < length) { + result[index] = iteratee(array[index], index, array); + } + return result; + } + + var _arrayMap = arrayMap; + + /** `Object#toString` result references. */ + var symbolTag$1 = '[object Symbol]'; + + /** + * Checks if `value` is classified as a `Symbol` primitive or object. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. + * @example + * + * _.isSymbol(Symbol.iterator); + * // => true + * + * _.isSymbol('abc'); + * // => false + */ + function isSymbol(value) { + return typeof value == 'symbol' || + (isObjectLike_1(value) && _baseGetTag(value) == symbolTag$1); + } + + var isSymbol_1 = isSymbol; + + /** Used to match property names within property paths. */ + var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/, + reIsPlainProp = /^\w*$/; + + /** + * Checks if `value` is a property name and not a property path. + * + * @private + * @param {*} value The value to check. + * @param {Object} [object] The object to query keys on. + * @returns {boolean} Returns `true` if `value` is a property name, else `false`. + */ + function isKey(value, object) { + if (isArray_1(value)) { + return false; + } + var type = typeof value; + if (type == 'number' || type == 'symbol' || type == 'boolean' || + value == null || isSymbol_1(value)) { + return true; + } + return reIsPlainProp.test(value) || !reIsDeepProp.test(value) || + (object != null && value in Object(object)); + } + + var _isKey = isKey; + + /* Built-in method references that are verified to be native. */ + var nativeCreate = _getNative(Object, 'create'); + + var _nativeCreate = nativeCreate; + + /** + * Removes all key-value entries from the hash. + * + * @private + * @name clear + * @memberOf Hash + */ + function hashClear() { + this.__data__ = _nativeCreate ? _nativeCreate(null) : {}; + this.size = 0; + } + + var _hashClear = hashClear; + + /** + * Removes `key` and its value from the hash. + * + * @private + * @name delete + * @memberOf Hash + * @param {Object} hash The hash to modify. + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ + function hashDelete(key) { + var result = this.has(key) && delete this.__data__[key]; + this.size -= result ? 1 : 0; + return result; + } + + var _hashDelete = hashDelete; + + /** Used to stand-in for `undefined` hash values. */ + var HASH_UNDEFINED$2 = '__lodash_hash_undefined__'; + + /** Used for built-in method references. */ + var objectProto$5 = Object.prototype; + + /** Used to check objects for own properties. */ + var hasOwnProperty$5 = objectProto$5.hasOwnProperty; + + /** + * Gets the hash value for `key`. + * + * @private + * @name get + * @memberOf Hash + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ + function hashGet(key) { + var data = this.__data__; + if (_nativeCreate) { + var result = data[key]; + return result === HASH_UNDEFINED$2 ? undefined : result; + } + return hasOwnProperty$5.call(data, key) ? data[key] : undefined; + } + + var _hashGet = hashGet; + + /** Used for built-in method references. */ + var objectProto$4 = Object.prototype; + + /** Used to check objects for own properties. */ + var hasOwnProperty$4 = objectProto$4.hasOwnProperty; + + /** + * Checks if a hash value for `key` exists. + * + * @private + * @name has + * @memberOf Hash + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function hashHas(key) { + var data = this.__data__; + return _nativeCreate ? (data[key] !== undefined) : hasOwnProperty$4.call(data, key); + } + + var _hashHas = hashHas; + + /** Used to stand-in for `undefined` hash values. */ + var HASH_UNDEFINED$1 = '__lodash_hash_undefined__'; + + /** + * Sets the hash `key` to `value`. + * + * @private + * @name set + * @memberOf Hash + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the hash instance. + */ + function hashSet(key, value) { + var data = this.__data__; + this.size += this.has(key) ? 0 : 1; + data[key] = (_nativeCreate && value === undefined) ? HASH_UNDEFINED$1 : value; + return this; + } + + var _hashSet = hashSet; + + /** + * Creates a hash object. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ + function Hash(entries) { + var index = -1, + length = entries == null ? 0 : entries.length; + + this.clear(); + while (++index < length) { + var entry = entries[index]; + this.set(entry[0], entry[1]); + } + } + + // Add methods to `Hash`. + Hash.prototype.clear = _hashClear; + Hash.prototype['delete'] = _hashDelete; + Hash.prototype.get = _hashGet; + Hash.prototype.has = _hashHas; + Hash.prototype.set = _hashSet; + + var _Hash = Hash; + + /** + * Removes all key-value entries from the list cache. + * + * @private + * @name clear + * @memberOf ListCache + */ + function listCacheClear() { + this.__data__ = []; + this.size = 0; + } + + var _listCacheClear = listCacheClear; + + /** + * Performs a + * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * comparison between two values to determine if they are equivalent. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + * @example + * + * var object = { 'a': 1 }; + * var other = { 'a': 1 }; + * + * _.eq(object, object); + * // => true + * + * _.eq(object, other); + * // => false + * + * _.eq('a', 'a'); + * // => true + * + * _.eq('a', Object('a')); + * // => false + * + * _.eq(NaN, NaN); + * // => true + */ + function eq(value, other) { + return value === other || (value !== value && other !== other); + } + + var eq_1 = eq; + + /** + * Gets the index at which the `key` is found in `array` of key-value pairs. + * + * @private + * @param {Array} array The array to inspect. + * @param {*} key The key to search for. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function assocIndexOf(array, key) { + var length = array.length; + while (length--) { + if (eq_1(array[length][0], key)) { + return length; + } + } + return -1; + } + + var _assocIndexOf = assocIndexOf; + + /** Used for built-in method references. */ + var arrayProto = Array.prototype; + + /** Built-in value references. */ + var splice = arrayProto.splice; + + /** + * Removes `key` and its value from the list cache. + * + * @private + * @name delete + * @memberOf ListCache + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ + function listCacheDelete(key) { + var data = this.__data__, + index = _assocIndexOf(data, key); + + if (index < 0) { + return false; + } + var lastIndex = data.length - 1; + if (index == lastIndex) { + data.pop(); + } else { + splice.call(data, index, 1); + } + --this.size; + return true; + } + + var _listCacheDelete = listCacheDelete; + + /** + * Gets the list cache value for `key`. + * + * @private + * @name get + * @memberOf ListCache + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ + function listCacheGet(key) { + var data = this.__data__, + index = _assocIndexOf(data, key); + + return index < 0 ? undefined : data[index][1]; + } + + var _listCacheGet = listCacheGet; + + /** + * Checks if a list cache value for `key` exists. + * + * @private + * @name has + * @memberOf ListCache + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function listCacheHas(key) { + return _assocIndexOf(this.__data__, key) > -1; + } + + var _listCacheHas = listCacheHas; + + /** + * Sets the list cache `key` to `value`. + * + * @private + * @name set + * @memberOf ListCache + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the list cache instance. + */ + function listCacheSet(key, value) { + var data = this.__data__, + index = _assocIndexOf(data, key); + + if (index < 0) { + ++this.size; + data.push([key, value]); + } else { + data[index][1] = value; + } + return this; + } + + var _listCacheSet = listCacheSet; + + /** + * Creates an list cache object. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ + function ListCache(entries) { + var index = -1, + length = entries == null ? 0 : entries.length; + + this.clear(); + while (++index < length) { + var entry = entries[index]; + this.set(entry[0], entry[1]); + } + } + + // Add methods to `ListCache`. + ListCache.prototype.clear = _listCacheClear; + ListCache.prototype['delete'] = _listCacheDelete; + ListCache.prototype.get = _listCacheGet; + ListCache.prototype.has = _listCacheHas; + ListCache.prototype.set = _listCacheSet; + + var _ListCache = ListCache; + + /** + * Removes all key-value entries from the map. + * + * @private + * @name clear + * @memberOf MapCache + */ + function mapCacheClear() { + this.size = 0; + this.__data__ = { + 'hash': new _Hash, + 'map': new (_Map || _ListCache), + 'string': new _Hash + }; + } + + var _mapCacheClear = mapCacheClear; + + /** + * Checks if `value` is suitable for use as unique object key. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is suitable, else `false`. + */ + function isKeyable(value) { + var type = typeof value; + return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean') + ? (value !== '__proto__') + : (value === null); + } + + var _isKeyable = isKeyable; + + /** + * Gets the data for `map`. + * + * @private + * @param {Object} map The map to query. + * @param {string} key The reference key. + * @returns {*} Returns the map data. + */ + function getMapData(map, key) { + var data = map.__data__; + return _isKeyable(key) + ? data[typeof key == 'string' ? 'string' : 'hash'] + : data.map; + } + + var _getMapData = getMapData; + + /** + * Removes `key` and its value from the map. + * + * @private + * @name delete + * @memberOf MapCache + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ + function mapCacheDelete(key) { + var result = _getMapData(this, key)['delete'](key); + this.size -= result ? 1 : 0; + return result; + } + + var _mapCacheDelete = mapCacheDelete; + + /** + * Gets the map value for `key`. + * + * @private + * @name get + * @memberOf MapCache + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ + function mapCacheGet(key) { + return _getMapData(this, key).get(key); + } + + var _mapCacheGet = mapCacheGet; + + /** + * Checks if a map value for `key` exists. + * + * @private + * @name has + * @memberOf MapCache + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function mapCacheHas(key) { + return _getMapData(this, key).has(key); + } + + var _mapCacheHas = mapCacheHas; + + /** + * Sets the map `key` to `value`. + * + * @private + * @name set + * @memberOf MapCache + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the map cache instance. + */ + function mapCacheSet(key, value) { + var data = _getMapData(this, key), + size = data.size; + + data.set(key, value); + this.size += data.size == size ? 0 : 1; + return this; + } + + var _mapCacheSet = mapCacheSet; + + /** + * Creates a map cache object to store key-value pairs. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ + function MapCache(entries) { + var index = -1, + length = entries == null ? 0 : entries.length; + + this.clear(); + while (++index < length) { + var entry = entries[index]; + this.set(entry[0], entry[1]); + } + } + + // Add methods to `MapCache`. + MapCache.prototype.clear = _mapCacheClear; + MapCache.prototype['delete'] = _mapCacheDelete; + MapCache.prototype.get = _mapCacheGet; + MapCache.prototype.has = _mapCacheHas; + MapCache.prototype.set = _mapCacheSet; + + var _MapCache = MapCache; + + /** Error message constants. */ + var FUNC_ERROR_TEXT = 'Expected a function'; + + /** + * Creates a function that memoizes the result of `func`. If `resolver` is + * provided, it determines the cache key for storing the result based on the + * arguments provided to the memoized function. By default, the first argument + * provided to the memoized function is used as the map cache key. The `func` + * is invoked with the `this` binding of the memoized function. + * + * **Note:** The cache is exposed as the `cache` property on the memoized + * function. Its creation may be customized by replacing the `_.memoize.Cache` + * constructor with one whose instances implement the + * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object) + * method interface of `clear`, `delete`, `get`, `has`, and `set`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Function + * @param {Function} func The function to have its output memoized. + * @param {Function} [resolver] The function to resolve the cache key. + * @returns {Function} Returns the new memoized function. + * @example + * + * var object = { 'a': 1, 'b': 2 }; + * var other = { 'c': 3, 'd': 4 }; + * + * var values = _.memoize(_.values); + * values(object); + * // => [1, 2] + * + * values(other); + * // => [3, 4] + * + * object.a = 2; + * values(object); + * // => [1, 2] + * + * // Modify the result cache. + * values.cache.set(object, ['a', 'b']); + * values(object); + * // => ['a', 'b'] + * + * // Replace `_.memoize.Cache`. + * _.memoize.Cache = WeakMap; + */ + function memoize(func, resolver) { + if (typeof func != 'function' || (resolver != null && typeof resolver != 'function')) { + throw new TypeError(FUNC_ERROR_TEXT); + } + var memoized = function() { + var args = arguments, + key = resolver ? resolver.apply(this, args) : args[0], + cache = memoized.cache; + + if (cache.has(key)) { + return cache.get(key); + } + var result = func.apply(this, args); + memoized.cache = cache.set(key, result) || cache; + return result; + }; + memoized.cache = new (memoize.Cache || _MapCache); + return memoized; + } + + // Expose `MapCache`. + memoize.Cache = _MapCache; + + var memoize_1 = memoize; + + /** Used as the maximum memoize cache size. */ + var MAX_MEMOIZE_SIZE = 500; + + /** + * A specialized version of `_.memoize` which clears the memoized function's + * cache when it exceeds `MAX_MEMOIZE_SIZE`. + * + * @private + * @param {Function} func The function to have its output memoized. + * @returns {Function} Returns the new memoized function. + */ + function memoizeCapped(func) { + var result = memoize_1(func, function(key) { + if (cache.size === MAX_MEMOIZE_SIZE) { + cache.clear(); + } + return key; + }); + + var cache = result.cache; + return result; + } + + var _memoizeCapped = memoizeCapped; + + /** Used to match property names within property paths. */ + var rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g; + + /** Used to match backslashes in property paths. */ + var reEscapeChar = /\\(\\)?/g; + + /** + * Converts `string` to a property path array. + * + * @private + * @param {string} string The string to convert. + * @returns {Array} Returns the property path array. + */ + var stringToPath = _memoizeCapped(function(string) { + var result = []; + if (string.charCodeAt(0) === 46 /* . */) { + result.push(''); + } + string.replace(rePropName, function(match, number, quote, subString) { + result.push(quote ? subString.replace(reEscapeChar, '$1') : (number || match)); + }); + return result; + }); + + var _stringToPath = stringToPath; + + /** Used to convert symbols to primitives and strings. */ + var symbolProto$1 = _Symbol ? _Symbol.prototype : undefined, + symbolToString = symbolProto$1 ? symbolProto$1.toString : undefined; + + /** + * The base implementation of `_.toString` which doesn't convert nullish + * values to empty strings. + * + * @private + * @param {*} value The value to process. + * @returns {string} Returns the string. + */ + function baseToString(value) { + // Exit early for strings to avoid a performance hit in some environments. + if (typeof value == 'string') { + return value; + } + if (isArray_1(value)) { + // Recursively convert values (susceptible to call stack limits). + return _arrayMap(value, baseToString) + ''; + } + if (isSymbol_1(value)) { + return symbolToString ? symbolToString.call(value) : ''; + } + var result = (value + ''); + return (result == '0' && (1 / value) == -Infinity) ? '-0' : result; + } + + var _baseToString = baseToString; + + /** + * Converts `value` to a string. An empty string is returned for `null` + * and `undefined` values. The sign of `-0` is preserved. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {string} Returns the converted string. + * @example + * + * _.toString(null); + * // => '' + * + * _.toString(-0); + * // => '-0' + * + * _.toString([1, 2, 3]); + * // => '1,2,3' + */ + function toString$1(value) { + return value == null ? '' : _baseToString(value); + } + + var toString_1 = toString$1; + + /** + * Casts `value` to a path array if it's not one. + * + * @private + * @param {*} value The value to inspect. + * @param {Object} [object] The object to query keys on. + * @returns {Array} Returns the cast property path array. + */ + function castPath(value, object) { + if (isArray_1(value)) { + return value; + } + return _isKey(value, object) ? [value] : _stringToPath(toString_1(value)); + } + + var _castPath = castPath; + + /** + * Converts `value` to a string key if it's not a string or symbol. + * + * @private + * @param {*} value The value to inspect. + * @returns {string|symbol} Returns the key. + */ + function toKey(value) { + if (typeof value == 'string' || isSymbol_1(value)) { + return value; + } + var result = (value + ''); + return (result == '0' && (1 / value) == -Infinity) ? '-0' : result; + } + + var _toKey = toKey; + + /** + * The base implementation of `_.get` without support for default values. + * + * @private + * @param {Object} object The object to query. + * @param {Array|string} path The path of the property to get. + * @returns {*} Returns the resolved value. + */ + function baseGet(object, path) { + path = _castPath(path, object); + + var index = 0, + length = path.length; + + while (object != null && index < length) { + object = object[_toKey(path[index++])]; + } + return (index && index == length) ? object : undefined; + } + + var _baseGet = baseGet; + + /** + * Removes all key-value entries from the stack. + * + * @private + * @name clear + * @memberOf Stack + */ + function stackClear() { + this.__data__ = new _ListCache; + this.size = 0; + } + + var _stackClear = stackClear; + + /** + * Removes `key` and its value from the stack. + * + * @private + * @name delete + * @memberOf Stack + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ + function stackDelete(key) { + var data = this.__data__, + result = data['delete'](key); + + this.size = data.size; + return result; + } + + var _stackDelete = stackDelete; + + /** + * Gets the stack value for `key`. + * + * @private + * @name get + * @memberOf Stack + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ + function stackGet(key) { + return this.__data__.get(key); + } + + var _stackGet = stackGet; + + /** + * Checks if a stack value for `key` exists. + * + * @private + * @name has + * @memberOf Stack + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function stackHas(key) { + return this.__data__.has(key); + } + + var _stackHas = stackHas; + + /** Used as the size to enable large array optimizations. */ + var LARGE_ARRAY_SIZE = 200; + + /** + * Sets the stack `key` to `value`. + * + * @private + * @name set + * @memberOf Stack + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the stack cache instance. + */ + function stackSet(key, value) { + var data = this.__data__; + if (data instanceof _ListCache) { + var pairs = data.__data__; + if (!_Map || (pairs.length < LARGE_ARRAY_SIZE - 1)) { + pairs.push([key, value]); + this.size = ++data.size; + return this; + } + data = this.__data__ = new _MapCache(pairs); + } + data.set(key, value); + this.size = data.size; + return this; + } + + var _stackSet = stackSet; + + /** + * Creates a stack cache object to store key-value pairs. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ + function Stack(entries) { + var data = this.__data__ = new _ListCache(entries); + this.size = data.size; + } + + // Add methods to `Stack`. + Stack.prototype.clear = _stackClear; + Stack.prototype['delete'] = _stackDelete; + Stack.prototype.get = _stackGet; + Stack.prototype.has = _stackHas; + Stack.prototype.set = _stackSet; + + var _Stack = Stack; + + /** Used to stand-in for `undefined` hash values. */ + var HASH_UNDEFINED = '__lodash_hash_undefined__'; + + /** + * Adds `value` to the array cache. + * + * @private + * @name add + * @memberOf SetCache + * @alias push + * @param {*} value The value to cache. + * @returns {Object} Returns the cache instance. + */ + function setCacheAdd(value) { + this.__data__.set(value, HASH_UNDEFINED); + return this; + } + + var _setCacheAdd = setCacheAdd; + + /** + * Checks if `value` is in the array cache. + * + * @private + * @name has + * @memberOf SetCache + * @param {*} value The value to search for. + * @returns {boolean} Returns `true` if `value` is found, else `false`. + */ + function setCacheHas(value) { + return this.__data__.has(value); + } + + var _setCacheHas = setCacheHas; + + /** + * + * Creates an array cache object to store unique values. + * + * @private + * @constructor + * @param {Array} [values] The values to cache. + */ + function SetCache(values) { + var index = -1, + length = values == null ? 0 : values.length; + + this.__data__ = new _MapCache; + while (++index < length) { + this.add(values[index]); + } + } + + // Add methods to `SetCache`. + SetCache.prototype.add = SetCache.prototype.push = _setCacheAdd; + SetCache.prototype.has = _setCacheHas; + + var _SetCache = SetCache; + + /** + * A specialized version of `_.some` for arrays without support for iteratee + * shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {boolean} Returns `true` if any element passes the predicate check, + * else `false`. + */ + function arraySome(array, predicate) { + var index = -1, + length = array == null ? 0 : array.length; + + while (++index < length) { + if (predicate(array[index], index, array)) { + return true; + } + } + return false; + } + + var _arraySome = arraySome; + + /** + * Checks if a `cache` value for `key` exists. + * + * @private + * @param {Object} cache The cache to query. + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function cacheHas(cache, key) { + return cache.has(key); + } + + var _cacheHas = cacheHas; + + /** Used to compose bitmasks for value comparisons. */ + var COMPARE_PARTIAL_FLAG$5 = 1, + COMPARE_UNORDERED_FLAG$3 = 2; + + /** + * A specialized version of `baseIsEqualDeep` for arrays with support for + * partial deep comparisons. + * + * @private + * @param {Array} array The array to compare. + * @param {Array} other The other array to compare. + * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. + * @param {Function} customizer The function to customize comparisons. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Object} stack Tracks traversed `array` and `other` objects. + * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`. + */ + function equalArrays(array, other, bitmask, customizer, equalFunc, stack) { + var isPartial = bitmask & COMPARE_PARTIAL_FLAG$5, + arrLength = array.length, + othLength = other.length; + + if (arrLength != othLength && !(isPartial && othLength > arrLength)) { + return false; + } + // Check that cyclic values are equal. + var arrStacked = stack.get(array); + var othStacked = stack.get(other); + if (arrStacked && othStacked) { + return arrStacked == other && othStacked == array; + } + var index = -1, + result = true, + seen = (bitmask & COMPARE_UNORDERED_FLAG$3) ? new _SetCache : undefined; + + stack.set(array, other); + stack.set(other, array); + + // Ignore non-index properties. + while (++index < arrLength) { + var arrValue = array[index], + othValue = other[index]; + + if (customizer) { + var compared = isPartial + ? customizer(othValue, arrValue, index, other, array, stack) + : customizer(arrValue, othValue, index, array, other, stack); + } + if (compared !== undefined) { + if (compared) { + continue; + } + result = false; + break; + } + // Recursively compare arrays (susceptible to call stack limits). + if (seen) { + if (!_arraySome(other, function(othValue, othIndex) { + if (!_cacheHas(seen, othIndex) && + (arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) { + return seen.push(othIndex); + } + })) { + result = false; + break; + } + } else if (!( + arrValue === othValue || + equalFunc(arrValue, othValue, bitmask, customizer, stack) + )) { + result = false; + break; + } + } + stack['delete'](array); + stack['delete'](other); + return result; + } + + var _equalArrays = equalArrays; + + /** Built-in value references. */ + var Uint8Array$1 = _root.Uint8Array; + + var _Uint8Array = Uint8Array$1; + + /** + * Converts `map` to its key-value pairs. + * + * @private + * @param {Object} map The map to convert. + * @returns {Array} Returns the key-value pairs. + */ + function mapToArray(map) { + var index = -1, + result = Array(map.size); + + map.forEach(function(value, key) { + result[++index] = [key, value]; + }); + return result; + } + + var _mapToArray = mapToArray; + + /** + * Converts `set` to an array of its values. + * + * @private + * @param {Object} set The set to convert. + * @returns {Array} Returns the values. + */ + function setToArray(set) { + var index = -1, + result = Array(set.size); + + set.forEach(function(value) { + result[++index] = value; + }); + return result; + } + + var _setToArray = setToArray; + + /** Used to compose bitmasks for value comparisons. */ + var COMPARE_PARTIAL_FLAG$4 = 1, + COMPARE_UNORDERED_FLAG$2 = 2; + + /** `Object#toString` result references. */ + var boolTag$1 = '[object Boolean]', + dateTag = '[object Date]', + errorTag = '[object Error]', + mapTag = '[object Map]', + numberTag = '[object Number]', + regexpTag = '[object RegExp]', + setTag = '[object Set]', + stringTag = '[object String]', + symbolTag = '[object Symbol]'; + + var arrayBufferTag = '[object ArrayBuffer]', + dataViewTag = '[object DataView]'; + + /** Used to convert symbols to primitives and strings. */ + var symbolProto = _Symbol ? _Symbol.prototype : undefined, + symbolValueOf = symbolProto ? symbolProto.valueOf : undefined; + + /** + * A specialized version of `baseIsEqualDeep` for comparing objects of + * the same `toStringTag`. + * + * **Note:** This function only supports comparing values with tags of + * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {string} tag The `toStringTag` of the objects to compare. + * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. + * @param {Function} customizer The function to customize comparisons. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Object} stack Tracks traversed `object` and `other` objects. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ + function equalByTag(object, other, tag, bitmask, customizer, equalFunc, stack) { + switch (tag) { + case dataViewTag: + if ((object.byteLength != other.byteLength) || + (object.byteOffset != other.byteOffset)) { + return false; + } + object = object.buffer; + other = other.buffer; + + case arrayBufferTag: + if ((object.byteLength != other.byteLength) || + !equalFunc(new _Uint8Array(object), new _Uint8Array(other))) { + return false; + } + return true; + + case boolTag$1: + case dateTag: + case numberTag: + // Coerce booleans to `1` or `0` and dates to milliseconds. + // Invalid dates are coerced to `NaN`. + return eq_1(+object, +other); + + case errorTag: + return object.name == other.name && object.message == other.message; + + case regexpTag: + case stringTag: + // Coerce regexes to strings and treat strings, primitives and objects, + // as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring + // for more details. + return object == (other + ''); + + case mapTag: + var convert = _mapToArray; + + case setTag: + var isPartial = bitmask & COMPARE_PARTIAL_FLAG$4; + convert || (convert = _setToArray); + + if (object.size != other.size && !isPartial) { + return false; + } + // Assume cyclic values are equal. + var stacked = stack.get(object); + if (stacked) { + return stacked == other; + } + bitmask |= COMPARE_UNORDERED_FLAG$2; + + // Recursively compare objects (susceptible to call stack limits). + stack.set(object, other); + var result = _equalArrays(convert(object), convert(other), bitmask, customizer, equalFunc, stack); + stack['delete'](object); + return result; + + case symbolTag: + if (symbolValueOf) { + return symbolValueOf.call(object) == symbolValueOf.call(other); + } + } + return false; + } + + var _equalByTag = equalByTag; + + /** + * The base implementation of `getAllKeys` and `getAllKeysIn` which uses + * `keysFunc` and `symbolsFunc` to get the enumerable property names and + * symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @param {Function} keysFunc The function to get the keys of `object`. + * @param {Function} symbolsFunc The function to get the symbols of `object`. + * @returns {Array} Returns the array of property names and symbols. + */ + function baseGetAllKeys(object, keysFunc, symbolsFunc) { + var result = keysFunc(object); + return isArray_1(object) ? result : _arrayPush(result, symbolsFunc(object)); + } + + var _baseGetAllKeys = baseGetAllKeys; + + /** + * A specialized version of `_.filter` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {Array} Returns the new filtered array. + */ + function arrayFilter(array, predicate) { + var index = -1, + length = array == null ? 0 : array.length, + resIndex = 0, + result = []; + + while (++index < length) { + var value = array[index]; + if (predicate(value, index, array)) { + result[resIndex++] = value; + } + } + return result; + } + + var _arrayFilter = arrayFilter; + + /** + * This method returns a new empty array. + * + * @static + * @memberOf _ + * @since 4.13.0 + * @category Util + * @returns {Array} Returns the new empty array. + * @example + * + * var arrays = _.times(2, _.stubArray); + * + * console.log(arrays); + * // => [[], []] + * + * console.log(arrays[0] === arrays[1]); + * // => false + */ + function stubArray() { + return []; + } + + var stubArray_1 = stubArray; + + /** Used for built-in method references. */ + var objectProto$3 = Object.prototype; + + /** Built-in value references. */ + var propertyIsEnumerable = objectProto$3.propertyIsEnumerable; + + /* Built-in method references for those with the same name as other `lodash` methods. */ + var nativeGetSymbols = Object.getOwnPropertySymbols; + + /** + * Creates an array of the own enumerable symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of symbols. + */ + var getSymbols = !nativeGetSymbols ? stubArray_1 : function(object) { + if (object == null) { + return []; + } + object = Object(object); + return _arrayFilter(nativeGetSymbols(object), function(symbol) { + return propertyIsEnumerable.call(object, symbol); + }); + }; + + var _getSymbols = getSymbols; + + /** + * The base implementation of `_.times` without support for iteratee shorthands + * or max array length checks. + * + * @private + * @param {number} n The number of times to invoke `iteratee`. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the array of results. + */ + function baseTimes(n, iteratee) { + var index = -1, + result = Array(n); + + while (++index < n) { + result[index] = iteratee(index); + } + return result; + } + + var _baseTimes = baseTimes; + + /** Used as references for various `Number` constants. */ + var MAX_SAFE_INTEGER = 9007199254740991; + + /** Used to detect unsigned integer values. */ + var reIsUint = /^(?:0|[1-9]\d*)$/; + + /** + * Checks if `value` is a valid array-like index. + * + * @private + * @param {*} value The value to check. + * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. + * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. + */ + function isIndex(value, length) { + var type = typeof value; + length = length == null ? MAX_SAFE_INTEGER : length; + + return !!length && + (type == 'number' || + (type != 'symbol' && reIsUint.test(value))) && + (value > -1 && value % 1 == 0 && value < length); + } + + var _isIndex = isIndex; + + /** Used for built-in method references. */ + var objectProto$2 = Object.prototype; + + /** Used to check objects for own properties. */ + var hasOwnProperty$3 = objectProto$2.hasOwnProperty; + + /** + * Creates an array of the enumerable property names of the array-like `value`. + * + * @private + * @param {*} value The value to query. + * @param {boolean} inherited Specify returning inherited property names. + * @returns {Array} Returns the array of property names. + */ + function arrayLikeKeys(value, inherited) { + var isArr = isArray_1(value), + isArg = !isArr && isArguments_1(value), + isBuff = !isArr && !isArg && isBuffer_1(value), + isType = !isArr && !isArg && !isBuff && isTypedArray_1(value), + skipIndexes = isArr || isArg || isBuff || isType, + result = skipIndexes ? _baseTimes(value.length, String) : [], + length = result.length; + + for (var key in value) { + if ((inherited || hasOwnProperty$3.call(value, key)) && + !(skipIndexes && ( + // Safari 9 has enumerable `arguments.length` in strict mode. + key == 'length' || + // Node.js 0.10 has enumerable non-index properties on buffers. + (isBuff && (key == 'offset' || key == 'parent')) || + // PhantomJS 2 has enumerable non-index properties on typed arrays. + (isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset')) || + // Skip index properties. + _isIndex(key, length) + ))) { + result.push(key); + } + } + return result; + } + + var _arrayLikeKeys = arrayLikeKeys; + + /** + * Creates an array of the own enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. See the + * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) + * for more details. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keys(new Foo); + * // => ['a', 'b'] (iteration order is not guaranteed) + * + * _.keys('hi'); + * // => ['0', '1'] + */ + function keys(object) { + return isArrayLike_1(object) ? _arrayLikeKeys(object) : _baseKeys(object); + } + + var keys_1 = keys; + + /** + * Creates an array of own enumerable property names and symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names and symbols. + */ + function getAllKeys(object) { + return _baseGetAllKeys(object, keys_1, _getSymbols); + } + + var _getAllKeys = getAllKeys; + + /** Used to compose bitmasks for value comparisons. */ + var COMPARE_PARTIAL_FLAG$3 = 1; + + /** Used for built-in method references. */ + var objectProto$1 = Object.prototype; + + /** Used to check objects for own properties. */ + var hasOwnProperty$2 = objectProto$1.hasOwnProperty; + + /** + * A specialized version of `baseIsEqualDeep` for objects with support for + * partial deep comparisons. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. + * @param {Function} customizer The function to customize comparisons. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Object} stack Tracks traversed `object` and `other` objects. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ + function equalObjects(object, other, bitmask, customizer, equalFunc, stack) { + var isPartial = bitmask & COMPARE_PARTIAL_FLAG$3, + objProps = _getAllKeys(object), + objLength = objProps.length, + othProps = _getAllKeys(other), + othLength = othProps.length; + + if (objLength != othLength && !isPartial) { + return false; + } + var index = objLength; + while (index--) { + var key = objProps[index]; + if (!(isPartial ? key in other : hasOwnProperty$2.call(other, key))) { + return false; + } + } + // Check that cyclic values are equal. + var objStacked = stack.get(object); + var othStacked = stack.get(other); + if (objStacked && othStacked) { + return objStacked == other && othStacked == object; + } + var result = true; + stack.set(object, other); + stack.set(other, object); + + var skipCtor = isPartial; + while (++index < objLength) { + key = objProps[index]; + var objValue = object[key], + othValue = other[key]; + + if (customizer) { + var compared = isPartial + ? customizer(othValue, objValue, key, other, object, stack) + : customizer(objValue, othValue, key, object, other, stack); + } + // Recursively compare objects (susceptible to call stack limits). + if (!(compared === undefined + ? (objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack)) + : compared + )) { + result = false; + break; + } + skipCtor || (skipCtor = key == 'constructor'); + } + if (result && !skipCtor) { + var objCtor = object.constructor, + othCtor = other.constructor; + + // Non `Object` object instances with different constructors are not equal. + if (objCtor != othCtor && + ('constructor' in object && 'constructor' in other) && + !(typeof objCtor == 'function' && objCtor instanceof objCtor && + typeof othCtor == 'function' && othCtor instanceof othCtor)) { + result = false; + } + } + stack['delete'](object); + stack['delete'](other); + return result; + } + + var _equalObjects = equalObjects; + + /** Used to compose bitmasks for value comparisons. */ + var COMPARE_PARTIAL_FLAG$2 = 1; + + /** `Object#toString` result references. */ + var argsTag = '[object Arguments]', + arrayTag = '[object Array]', + objectTag = '[object Object]'; + + /** Used for built-in method references. */ + var objectProto = Object.prototype; + + /** Used to check objects for own properties. */ + var hasOwnProperty$1 = objectProto.hasOwnProperty; + + /** + * A specialized version of `baseIsEqual` for arrays and objects which performs + * deep comparisons and tracks traversed objects enabling objects with circular + * references to be compared. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. + * @param {Function} customizer The function to customize comparisons. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Object} [stack] Tracks traversed `object` and `other` objects. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ + function baseIsEqualDeep(object, other, bitmask, customizer, equalFunc, stack) { + var objIsArr = isArray_1(object), + othIsArr = isArray_1(other), + objTag = objIsArr ? arrayTag : _getTag(object), + othTag = othIsArr ? arrayTag : _getTag(other); + + objTag = objTag == argsTag ? objectTag : objTag; + othTag = othTag == argsTag ? objectTag : othTag; + + var objIsObj = objTag == objectTag, + othIsObj = othTag == objectTag, + isSameTag = objTag == othTag; + + if (isSameTag && isBuffer_1(object)) { + if (!isBuffer_1(other)) { + return false; + } + objIsArr = true; + objIsObj = false; + } + if (isSameTag && !objIsObj) { + stack || (stack = new _Stack); + return (objIsArr || isTypedArray_1(object)) + ? _equalArrays(object, other, bitmask, customizer, equalFunc, stack) + : _equalByTag(object, other, objTag, bitmask, customizer, equalFunc, stack); + } + if (!(bitmask & COMPARE_PARTIAL_FLAG$2)) { + var objIsWrapped = objIsObj && hasOwnProperty$1.call(object, '__wrapped__'), + othIsWrapped = othIsObj && hasOwnProperty$1.call(other, '__wrapped__'); + + if (objIsWrapped || othIsWrapped) { + var objUnwrapped = objIsWrapped ? object.value() : object, + othUnwrapped = othIsWrapped ? other.value() : other; + + stack || (stack = new _Stack); + return equalFunc(objUnwrapped, othUnwrapped, bitmask, customizer, stack); + } + } + if (!isSameTag) { + return false; + } + stack || (stack = new _Stack); + return _equalObjects(object, other, bitmask, customizer, equalFunc, stack); + } + + var _baseIsEqualDeep = baseIsEqualDeep; + + /** + * The base implementation of `_.isEqual` which supports partial comparisons + * and tracks traversed objects. + * + * @private + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @param {boolean} bitmask The bitmask flags. + * 1 - Unordered comparison + * 2 - Partial comparison + * @param {Function} [customizer] The function to customize comparisons. + * @param {Object} [stack] Tracks traversed `value` and `other` objects. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + */ + function baseIsEqual(value, other, bitmask, customizer, stack) { + if (value === other) { + return true; + } + if (value == null || other == null || (!isObjectLike_1(value) && !isObjectLike_1(other))) { + return value !== value && other !== other; + } + return _baseIsEqualDeep(value, other, bitmask, customizer, baseIsEqual, stack); + } + + var _baseIsEqual = baseIsEqual; + + /** Used to compose bitmasks for value comparisons. */ + var COMPARE_PARTIAL_FLAG$1 = 1, + COMPARE_UNORDERED_FLAG$1 = 2; + + /** + * The base implementation of `_.isMatch` without support for iteratee shorthands. + * + * @private + * @param {Object} object The object to inspect. + * @param {Object} source The object of property values to match. + * @param {Array} matchData The property names, values, and compare flags to match. + * @param {Function} [customizer] The function to customize comparisons. + * @returns {boolean} Returns `true` if `object` is a match, else `false`. + */ + function baseIsMatch(object, source, matchData, customizer) { + var index = matchData.length, + length = index, + noCustomizer = !customizer; + + if (object == null) { + return !length; + } + object = Object(object); + while (index--) { + var data = matchData[index]; + if ((noCustomizer && data[2]) + ? data[1] !== object[data[0]] + : !(data[0] in object) + ) { + return false; + } + } + while (++index < length) { + data = matchData[index]; + var key = data[0], + objValue = object[key], + srcValue = data[1]; + + if (noCustomizer && data[2]) { + if (objValue === undefined && !(key in object)) { + return false; + } + } else { + var stack = new _Stack; + if (customizer) { + var result = customizer(objValue, srcValue, key, object, source, stack); + } + if (!(result === undefined + ? _baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG$1 | COMPARE_UNORDERED_FLAG$1, customizer, stack) + : result + )) { + return false; + } + } + } + return true; + } + + var _baseIsMatch = baseIsMatch; + + /** + * Checks if `value` is suitable for strict equality comparisons, i.e. `===`. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` if suitable for strict + * equality comparisons, else `false`. + */ + function isStrictComparable(value) { + return value === value && !isObject_1(value); + } + + var _isStrictComparable = isStrictComparable; + + /** + * Gets the property names, values, and compare flags of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the match data of `object`. + */ + function getMatchData(object) { + var result = keys_1(object), + length = result.length; + + while (length--) { + var key = result[length], + value = object[key]; + + result[length] = [key, value, _isStrictComparable(value)]; + } + return result; + } + + var _getMatchData = getMatchData; + + /** + * A specialized version of `matchesProperty` for source values suitable + * for strict equality comparisons, i.e. `===`. + * + * @private + * @param {string} key The key of the property to get. + * @param {*} srcValue The value to match. + * @returns {Function} Returns the new spec function. + */ + function matchesStrictComparable(key, srcValue) { + return function(object) { + if (object == null) { + return false; + } + return object[key] === srcValue && + (srcValue !== undefined || (key in Object(object))); + }; + } + + var _matchesStrictComparable = matchesStrictComparable; + + /** + * The base implementation of `_.matches` which doesn't clone `source`. + * + * @private + * @param {Object} source The object of property values to match. + * @returns {Function} Returns the new spec function. + */ + function baseMatches(source) { + var matchData = _getMatchData(source); + if (matchData.length == 1 && matchData[0][2]) { + return _matchesStrictComparable(matchData[0][0], matchData[0][1]); + } + return function(object) { + return object === source || _baseIsMatch(object, source, matchData); + }; + } + + var _baseMatches = baseMatches; + + /** + * Gets the value at `path` of `object`. If the resolved value is + * `undefined`, the `defaultValue` is returned in its place. + * + * @static + * @memberOf _ + * @since 3.7.0 + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path of the property to get. + * @param {*} [defaultValue] The value returned for `undefined` resolved values. + * @returns {*} Returns the resolved value. + * @example + * + * var object = { 'a': [{ 'b': { 'c': 3 } }] }; + * + * _.get(object, 'a[0].b.c'); + * // => 3 + * + * _.get(object, ['a', '0', 'b', 'c']); + * // => 3 + * + * _.get(object, 'a.b.c', 'default'); + * // => 'default' + */ + function get(object, path, defaultValue) { + var result = object == null ? undefined : _baseGet(object, path); + return result === undefined ? defaultValue : result; + } + + var get_1 = get; + + /** + * The base implementation of `_.hasIn` without support for deep paths. + * + * @private + * @param {Object} [object] The object to query. + * @param {Array|string} key The key to check. + * @returns {boolean} Returns `true` if `key` exists, else `false`. + */ + function baseHasIn(object, key) { + return object != null && key in Object(object); + } + + var _baseHasIn = baseHasIn; + + /** + * Checks if `path` exists on `object`. + * + * @private + * @param {Object} object The object to query. + * @param {Array|string} path The path to check. + * @param {Function} hasFunc The function to check properties. + * @returns {boolean} Returns `true` if `path` exists, else `false`. + */ + function hasPath(object, path, hasFunc) { + path = _castPath(path, object); + + var index = -1, + length = path.length, + result = false; + + while (++index < length) { + var key = _toKey(path[index]); + if (!(result = object != null && hasFunc(object, key))) { + break; + } + object = object[key]; + } + if (result || ++index != length) { + return result; + } + length = object == null ? 0 : object.length; + return !!length && isLength_1(length) && _isIndex(key, length) && + (isArray_1(object) || isArguments_1(object)); + } + + var _hasPath = hasPath; + + /** + * Checks if `path` is a direct or inherited property of `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path to check. + * @returns {boolean} Returns `true` if `path` exists, else `false`. + * @example + * + * var object = _.create({ 'a': _.create({ 'b': 2 }) }); + * + * _.hasIn(object, 'a'); + * // => true + * + * _.hasIn(object, 'a.b'); + * // => true + * + * _.hasIn(object, ['a', 'b']); + * // => true + * + * _.hasIn(object, 'b'); + * // => false + */ + function hasIn(object, path) { + return object != null && _hasPath(object, path, _baseHasIn); + } + + var hasIn_1 = hasIn; + + /** Used to compose bitmasks for value comparisons. */ + var COMPARE_PARTIAL_FLAG = 1, + COMPARE_UNORDERED_FLAG = 2; + + /** + * The base implementation of `_.matchesProperty` which doesn't clone `srcValue`. + * + * @private + * @param {string} path The path of the property to get. + * @param {*} srcValue The value to match. + * @returns {Function} Returns the new spec function. + */ + function baseMatchesProperty(path, srcValue) { + if (_isKey(path) && _isStrictComparable(srcValue)) { + return _matchesStrictComparable(_toKey(path), srcValue); + } + return function(object) { + var objValue = get_1(object, path); + return (objValue === undefined && objValue === srcValue) + ? hasIn_1(object, path) + : _baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG); + }; + } + + var _baseMatchesProperty = baseMatchesProperty; + + /** + * This method returns the first argument it receives. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Util + * @param {*} value Any value. + * @returns {*} Returns `value`. + * @example + * + * var object = { 'a': 1 }; + * + * console.log(_.identity(object) === object); + * // => true + */ + function identity(value) { + return value; + } + + var identity_1 = identity; + + /** + * The base implementation of `_.property` without support for deep paths. + * + * @private + * @param {string} key The key of the property to get. + * @returns {Function} Returns the new accessor function. + */ + function baseProperty(key) { + return function(object) { + return object == null ? undefined : object[key]; + }; + } + + var _baseProperty = baseProperty; + + /** + * A specialized version of `baseProperty` which supports deep paths. + * + * @private + * @param {Array|string} path The path of the property to get. + * @returns {Function} Returns the new accessor function. + */ + function basePropertyDeep(path) { + return function(object) { + return _baseGet(object, path); + }; + } + + var _basePropertyDeep = basePropertyDeep; + + /** + * Creates a function that returns the value at `path` of a given object. + * + * @static + * @memberOf _ + * @since 2.4.0 + * @category Util + * @param {Array|string} path The path of the property to get. + * @returns {Function} Returns the new accessor function. + * @example + * + * var objects = [ + * { 'a': { 'b': 2 } }, + * { 'a': { 'b': 1 } } + * ]; + * + * _.map(objects, _.property('a.b')); + * // => [2, 1] + * + * _.map(_.sortBy(objects, _.property(['a', 'b'])), 'a.b'); + * // => [1, 2] + */ + function property(path) { + return _isKey(path) ? _baseProperty(_toKey(path)) : _basePropertyDeep(path); + } + + var property_1 = property; + + /** + * The base implementation of `_.iteratee`. + * + * @private + * @param {*} [value=_.identity] The value to convert to an iteratee. + * @returns {Function} Returns the iteratee. + */ + function baseIteratee(value) { + // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9. + // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details. + if (typeof value == 'function') { + return value; + } + if (value == null) { + return identity_1; + } + if (typeof value == 'object') { + return isArray_1(value) + ? _baseMatchesProperty(value[0], value[1]) + : _baseMatches(value); + } + return property_1(value); + } + + var _baseIteratee = baseIteratee; + + /** + * Creates a base function for methods like `_.forIn` and `_.forOwn`. + * + * @private + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new base function. + */ + function createBaseFor(fromRight) { + return function(object, iteratee, keysFunc) { + var index = -1, + iterable = Object(object), + props = keysFunc(object), + length = props.length; + + while (length--) { + var key = props[fromRight ? length : ++index]; + if (iteratee(iterable[key], key, iterable) === false) { + break; + } + } + return object; + }; + } + + var _createBaseFor = createBaseFor; + + /** + * The base implementation of `baseForOwn` which iterates over `object` + * properties returned by `keysFunc` and invokes `iteratee` for each property. + * Iteratee functions may exit iteration early by explicitly returning `false`. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {Function} keysFunc The function to get the keys of `object`. + * @returns {Object} Returns `object`. + */ + var baseFor = _createBaseFor(); + + var _baseFor = baseFor; + + /** + * The base implementation of `_.forOwn` without support for iteratee shorthands. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Object} Returns `object`. + */ + function baseForOwn(object, iteratee) { + return object && _baseFor(object, iteratee, keys_1); + } + + var _baseForOwn = baseForOwn; + + /** + * Creates a `baseEach` or `baseEachRight` function. + * + * @private + * @param {Function} eachFunc The function to iterate over a collection. + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new base function. + */ + function createBaseEach(eachFunc, fromRight) { + return function(collection, iteratee) { + if (collection == null) { + return collection; + } + if (!isArrayLike_1(collection)) { + return eachFunc(collection, iteratee); + } + var length = collection.length, + index = fromRight ? length : -1, + iterable = Object(collection); + + while ((fromRight ? index-- : ++index < length)) { + if (iteratee(iterable[index], index, iterable) === false) { + break; + } + } + return collection; + }; + } + + var _createBaseEach = createBaseEach; + + /** + * The base implementation of `_.forEach` without support for iteratee shorthands. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array|Object} Returns `collection`. + */ + var baseEach = _createBaseEach(_baseForOwn); + + var _baseEach = baseEach; + + /** + * The base implementation of `_.map` without support for iteratee shorthands. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the new mapped array. + */ + function baseMap(collection, iteratee) { + var index = -1, + result = isArrayLike_1(collection) ? Array(collection.length) : []; + + _baseEach(collection, function(value, key, collection) { + result[++index] = iteratee(value, key, collection); + }); + return result; + } + + var _baseMap = baseMap; + + /** + * The base implementation of `_.sortBy` which uses `comparer` to define the + * sort order of `array` and replaces criteria objects with their corresponding + * values. + * + * @private + * @param {Array} array The array to sort. + * @param {Function} comparer The function to define sort order. + * @returns {Array} Returns `array`. + */ + function baseSortBy(array, comparer) { + var length = array.length; + + array.sort(comparer); + while (length--) { + array[length] = array[length].value; + } + return array; + } + + var _baseSortBy = baseSortBy; + + /** + * Compares values to sort them in ascending order. + * + * @private + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {number} Returns the sort order indicator for `value`. + */ + function compareAscending(value, other) { + if (value !== other) { + var valIsDefined = value !== undefined, + valIsNull = value === null, + valIsReflexive = value === value, + valIsSymbol = isSymbol_1(value); + + var othIsDefined = other !== undefined, + othIsNull = other === null, + othIsReflexive = other === other, + othIsSymbol = isSymbol_1(other); + + if ((!othIsNull && !othIsSymbol && !valIsSymbol && value > other) || + (valIsSymbol && othIsDefined && othIsReflexive && !othIsNull && !othIsSymbol) || + (valIsNull && othIsDefined && othIsReflexive) || + (!valIsDefined && othIsReflexive) || + !valIsReflexive) { + return 1; + } + if ((!valIsNull && !valIsSymbol && !othIsSymbol && value < other) || + (othIsSymbol && valIsDefined && valIsReflexive && !valIsNull && !valIsSymbol) || + (othIsNull && valIsDefined && valIsReflexive) || + (!othIsDefined && valIsReflexive) || + !othIsReflexive) { + return -1; + } + } + return 0; + } + + var _compareAscending = compareAscending; + + /** + * Used by `_.orderBy` to compare multiple properties of a value to another + * and stable sort them. + * + * If `orders` is unspecified, all values are sorted in ascending order. Otherwise, + * specify an order of "desc" for descending or "asc" for ascending sort order + * of corresponding values. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {boolean[]|string[]} orders The order to sort by for each property. + * @returns {number} Returns the sort order indicator for `object`. + */ + function compareMultiple(object, other, orders) { + var index = -1, + objCriteria = object.criteria, + othCriteria = other.criteria, + length = objCriteria.length, + ordersLength = orders.length; + + while (++index < length) { + var result = _compareAscending(objCriteria[index], othCriteria[index]); + if (result) { + if (index >= ordersLength) { + return result; + } + var order = orders[index]; + return result * (order == 'desc' ? -1 : 1); + } + } + // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications + // that causes it, under certain circumstances, to provide the same value for + // `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247 + // for more details. + // + // This also ensures a stable sort in V8 and other engines. + // See https://bugs.chromium.org/p/v8/issues/detail?id=90 for more details. + return object.index - other.index; + } + + var _compareMultiple = compareMultiple; + + /** + * The base implementation of `_.orderBy` without param guards. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by. + * @param {string[]} orders The sort orders of `iteratees`. + * @returns {Array} Returns the new sorted array. + */ + function baseOrderBy(collection, iteratees, orders) { + if (iteratees.length) { + iteratees = _arrayMap(iteratees, function(iteratee) { + if (isArray_1(iteratee)) { + return function(value) { + return _baseGet(value, iteratee.length === 1 ? iteratee[0] : iteratee); + }; + } + return iteratee; + }); + } else { + iteratees = [identity_1]; + } + + var index = -1; + iteratees = _arrayMap(iteratees, _baseUnary(_baseIteratee)); + + var result = _baseMap(collection, function(value, key, collection) { + var criteria = _arrayMap(iteratees, function(iteratee) { + return iteratee(value); + }); + return { 'criteria': criteria, 'index': ++index, 'value': value }; + }); + + return _baseSortBy(result, function(object, other) { + return _compareMultiple(object, other, orders); + }); + } + + var _baseOrderBy = baseOrderBy; + + /** + * A faster alternative to `Function#apply`, this function invokes `func` + * with the `this` binding of `thisArg` and the arguments of `args`. + * + * @private + * @param {Function} func The function to invoke. + * @param {*} thisArg The `this` binding of `func`. + * @param {Array} args The arguments to invoke `func` with. + * @returns {*} Returns the result of `func`. + */ + function apply(func, thisArg, args) { + switch (args.length) { + case 0: return func.call(thisArg); + case 1: return func.call(thisArg, args[0]); + case 2: return func.call(thisArg, args[0], args[1]); + case 3: return func.call(thisArg, args[0], args[1], args[2]); + } + return func.apply(thisArg, args); + } + + var _apply = apply; + + /* Built-in method references for those with the same name as other `lodash` methods. */ + var nativeMax = Math.max; + + /** + * A specialized version of `baseRest` which transforms the rest array. + * + * @private + * @param {Function} func The function to apply a rest parameter to. + * @param {number} [start=func.length-1] The start position of the rest parameter. + * @param {Function} transform The rest array transform. + * @returns {Function} Returns the new function. + */ + function overRest(func, start, transform) { + start = nativeMax(start === undefined ? (func.length - 1) : start, 0); + return function() { + var args = arguments, + index = -1, + length = nativeMax(args.length - start, 0), + array = Array(length); + + while (++index < length) { + array[index] = args[start + index]; + } + index = -1; + var otherArgs = Array(start + 1); + while (++index < start) { + otherArgs[index] = args[index]; + } + otherArgs[start] = transform(array); + return _apply(func, this, otherArgs); + }; + } + + var _overRest = overRest; + + /** + * Creates a function that returns `value`. + * + * @static + * @memberOf _ + * @since 2.4.0 + * @category Util + * @param {*} value The value to return from the new function. + * @returns {Function} Returns the new constant function. + * @example + * + * var objects = _.times(2, _.constant({ 'a': 1 })); + * + * console.log(objects); + * // => [{ 'a': 1 }, { 'a': 1 }] + * + * console.log(objects[0] === objects[1]); + * // => true + */ + function constant(value) { + return function() { + return value; + }; + } + + var constant_1 = constant; + + var defineProperty = (function() { + try { + var func = _getNative(Object, 'defineProperty'); + func({}, '', {}); + return func; + } catch (e) {} + }()); + + var _defineProperty = defineProperty; + + /** + * The base implementation of `setToString` without support for hot loop shorting. + * + * @private + * @param {Function} func The function to modify. + * @param {Function} string The `toString` result. + * @returns {Function} Returns `func`. + */ + var baseSetToString = !_defineProperty ? identity_1 : function(func, string) { + return _defineProperty(func, 'toString', { + 'configurable': true, + 'enumerable': false, + 'value': constant_1(string), + 'writable': true + }); + }; + + var _baseSetToString = baseSetToString; + + /** Used to detect hot functions by number of calls within a span of milliseconds. */ + var HOT_COUNT = 800, + HOT_SPAN = 16; + + /* Built-in method references for those with the same name as other `lodash` methods. */ + var nativeNow = Date.now; + + /** + * Creates a function that'll short out and invoke `identity` instead + * of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN` + * milliseconds. + * + * @private + * @param {Function} func The function to restrict. + * @returns {Function} Returns the new shortable function. + */ + function shortOut(func) { + var count = 0, + lastCalled = 0; + + return function() { + var stamp = nativeNow(), + remaining = HOT_SPAN - (stamp - lastCalled); + + lastCalled = stamp; + if (remaining > 0) { + if (++count >= HOT_COUNT) { + return arguments[0]; + } + } else { + count = 0; + } + return func.apply(undefined, arguments); + }; + } + + var _shortOut = shortOut; + + /** + * Sets the `toString` method of `func` to return `string`. + * + * @private + * @param {Function} func The function to modify. + * @param {Function} string The `toString` result. + * @returns {Function} Returns `func`. + */ + var setToString = _shortOut(_baseSetToString); + + var _setToString = setToString; + + /** + * The base implementation of `_.rest` which doesn't validate or coerce arguments. + * + * @private + * @param {Function} func The function to apply a rest parameter to. + * @param {number} [start=func.length-1] The start position of the rest parameter. + * @returns {Function} Returns the new function. + */ + function baseRest(func, start) { + return _setToString(_overRest(func, start, identity_1), func + ''); + } + + var _baseRest = baseRest; + + /** + * Checks if the given arguments are from an iteratee call. + * + * @private + * @param {*} value The potential iteratee value argument. + * @param {*} index The potential iteratee index or key argument. + * @param {*} object The potential iteratee object argument. + * @returns {boolean} Returns `true` if the arguments are from an iteratee call, + * else `false`. + */ + function isIterateeCall(value, index, object) { + if (!isObject_1(object)) { + return false; + } + var type = typeof index; + if (type == 'number' + ? (isArrayLike_1(object) && _isIndex(index, object.length)) + : (type == 'string' && index in object) + ) { + return eq_1(object[index], value); + } + return false; + } + + var _isIterateeCall = isIterateeCall; + + /** + * Creates an array of elements, sorted in ascending order by the results of + * running each element in a collection thru each iteratee. This method + * performs a stable sort, that is, it preserves the original sort order of + * equal elements. The iteratees are invoked with one argument: (value). + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {...(Function|Function[])} [iteratees=[_.identity]] + * The iteratees to sort by. + * @returns {Array} Returns the new sorted array. + * @example + * + * var users = [ + * { 'user': 'fred', 'age': 48 }, + * { 'user': 'barney', 'age': 36 }, + * { 'user': 'fred', 'age': 30 }, + * { 'user': 'barney', 'age': 34 } + * ]; + * + * _.sortBy(users, [function(o) { return o.user; }]); + * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 30]] + * + * _.sortBy(users, ['user', 'age']); + * // => objects for [['barney', 34], ['barney', 36], ['fred', 30], ['fred', 48]] + */ + var sortBy = _baseRest(function(collection, iteratees) { + if (collection == null) { + return []; + } + var length = iteratees.length; + if (length > 1 && _isIterateeCall(collection, iteratees[0], iteratees[1])) { + iteratees = []; + } else if (length > 2 && _isIterateeCall(iteratees[0], iteratees[1], iteratees[2])) { + iteratees = [iteratees[0]]; + } + return _baseOrderBy(collection, _baseFlatten(iteratees, 1), []); + }); + + var sortBy_1 = sortBy; + + /** + * Configuration for custom action validation rules. + * Defines allowed positions, metadata IDs, data model IDs, and fields for each target + * type. + * + */ + const customActionValidationConfig = { + [CustomActionTarget.LIVEBOARD]: { + positions: [exports.CustomActionsPosition.PRIMARY, exports.CustomActionsPosition.MENU], + allowedMetadataIds: ['liveboardIds'], + allowedDataModelIds: [], + allowedFields: ['name', 'id', 'position', 'target', 'metadataIds', 'orgIds', 'groupIds'], + }, + [CustomActionTarget.VIZ]: { + positions: [exports.CustomActionsPosition.MENU, exports.CustomActionsPosition.PRIMARY, exports.CustomActionsPosition.CONTEXTMENU], + allowedMetadataIds: ['liveboardIds', 'vizIds', 'answerIds'], + allowedDataModelIds: ['modelIds', 'modelColumnNames'], + allowedFields: ['name', 'id', 'position', 'target', 'metadataIds', 'orgIds', 'groupIds', 'dataModelIds'], + }, + [CustomActionTarget.ANSWER]: { + positions: [exports.CustomActionsPosition.MENU, exports.CustomActionsPosition.PRIMARY, exports.CustomActionsPosition.CONTEXTMENU], + allowedMetadataIds: ['answerIds'], + allowedDataModelIds: ['modelIds', 'modelColumnNames'], + allowedFields: ['name', 'id', 'position', 'target', 'metadataIds', 'orgIds', 'groupIds', 'dataModelIds'], + }, + [CustomActionTarget.SPOTTER]: { + positions: [exports.CustomActionsPosition.MENU, exports.CustomActionsPosition.CONTEXTMENU], + allowedMetadataIds: [], + allowedDataModelIds: ['modelIds'], + allowedFields: ['name', 'id', 'position', 'target', 'orgIds', 'groupIds', 'dataModelIds'], + }, + }; + /** + * Validates a single custom action based on its target type + * @param action - The custom action to validate + * @param primaryActionsPerTarget - Map to track primary actions per target + * @returns CustomActionValidation with isValid flag and reason string + * + * @hidden + */ + const validateCustomAction = (action, primaryActionsPerTarget) => { + const { id: actionId, target: targetType, position, metadataIds, dataModelIds } = action; + // Check if target type is supported + if (!customActionValidationConfig[targetType]) { + const errorMessage = CUSTOM_ACTIONS_ERROR_MESSAGE.UNSUPPORTED_TARGET(actionId, targetType); + return { isValid: false, errors: [errorMessage] }; + } + const config = customActionValidationConfig[targetType]; + const errors = []; + // Validate position + if (!arrayIncludesString(config.positions, position)) { + const supportedPositions = config.positions.join(', '); + errors.push(CUSTOM_ACTIONS_ERROR_MESSAGE.INVALID_POSITION(position, targetType, supportedPositions)); + } + // Validate metadata IDs + if (metadataIds) { + const invalidMetadataIds = Object.keys(metadataIds).filter((key) => !arrayIncludesString(config.allowedMetadataIds, key)); + if (invalidMetadataIds.length > 0) { + const supportedMetadataIds = config.allowedMetadataIds.length > 0 ? config.allowedMetadataIds.join(', ') : 'none'; + errors.push(CUSTOM_ACTIONS_ERROR_MESSAGE.INVALID_METADATA_IDS(targetType, invalidMetadataIds, supportedMetadataIds)); + } + } + // Validate data model IDs + if (dataModelIds) { + const invalidDataModelIds = Object.keys(dataModelIds).filter((key) => !arrayIncludesString(config.allowedDataModelIds, key)); + if (invalidDataModelIds.length > 0) { + const supportedDataModelIds = config.allowedDataModelIds.length > 0 ? config.allowedDataModelIds.join(', ') : 'none'; + errors.push(CUSTOM_ACTIONS_ERROR_MESSAGE.INVALID_DATA_MODEL_IDS(targetType, invalidDataModelIds, supportedDataModelIds)); + } + } + // Validate allowed fields + const actionKeys = Object.keys(action); + const invalidFields = actionKeys.filter((key) => !arrayIncludesString(config.allowedFields, key)); + if (invalidFields.length > 0) { + const supportedFields = config.allowedFields.join(', '); + errors.push(CUSTOM_ACTIONS_ERROR_MESSAGE.INVALID_FIELDS(targetType, invalidFields, supportedFields)); + } + return { + isValid: errors.length === 0, + errors, + }; + }; + /** + * Validates basic action structure and required fields + * @param action - The action to validate + * @returns Object containing validation result and missing fields + * + * @hidden + */ + const validateActionStructure = (action) => { + if (!action || typeof action !== 'object') { + return { isValid: false, missingFields: [] }; + } + // Check for all missing required fields + const missingFields = ['id', 'name', 'target', 'position'].filter(field => !action[field]); + return { isValid: missingFields.length === 0, missingFields }; + }; + /** + * Checks for duplicate IDs among actions + * @param actions - Array of actions to check + * @returns Object containing filtered actions and duplicate errors + * + * @hidden + */ + const filterDuplicateIds = (actions) => { + const idMap = actions.reduce((map, action) => { + const list = map.get(action.id) || []; + list.push(action); + map.set(action.id, list); + return map; + }, new Map()); + const { actions: actionsWithUniqueIds, errors } = Array.from(idMap.entries()).reduce((acc, [id, actionsWithSameId]) => { + if (actionsWithSameId.length === 1) { + acc.actions.push(actionsWithSameId[0]); + } + else { + // Keep the first action and add error for duplicates + acc.actions.push(actionsWithSameId[0]); + const duplicateNames = actionsWithSameId.slice(1).map(action => action.name); + acc.errors.push(CUSTOM_ACTIONS_ERROR_MESSAGE.DUPLICATE_IDS(id, duplicateNames, actionsWithSameId[0].name)); + } + return acc; + }, { actions: [], errors: [] }); + return { actions: actionsWithUniqueIds, errors }; + }; + /** + * Validates and processes custom actions + * @param customActions - Array of custom actions to validate + * @returns Object containing valid actions and any validation errors + */ + const getCustomActions = (customActions) => { + const errors = []; + if (!customActions || !Array.isArray(customActions)) { + return { actions: [], errors: [] }; + } + // Step 1: Handle invalid actions first (null, undefined, missing required + // fields) + const validActions = customActions.filter(action => { + const validation = validateActionStructure(action); + if (!validation.isValid) { + if (!action || typeof action !== 'object') { + errors.push(CUSTOM_ACTIONS_ERROR_MESSAGE.INVALID_ACTION_OBJECT); + } + else { + errors.push(CUSTOM_ACTIONS_ERROR_MESSAGE.MISSING_REQUIRED_FIELDS(action.id, validation.missingFields)); + } + return false; + } + return true; + }); + // Step 2: Check for duplicate IDs among valid actions + const { actions: actionsWithUniqueIds, errors: duplicateErrors } = filterDuplicateIds(validActions); + // Add duplicate errors to the errors array + duplicateErrors.forEach(error => errors.push(error)); + // Step 3: Validate actions with unique IDs + const finalValidActions = []; + actionsWithUniqueIds.forEach((action) => { + const { isValid, errors: validationErrors } = validateCustomAction(action); + validationErrors.forEach(error => errors.push(error)); + if (isValid) { + finalValidActions.push(action); + } + }); + // Step 4: Collect warnings for long custom action names + const MAX_ACTION_NAME_LENGTH = 30; + const warnings = finalValidActions + .filter(action => action.name.length > MAX_ACTION_NAME_LENGTH) + .map(action => `Custom action name '${action.name}' exceeds ${MAX_ACTION_NAME_LENGTH} characters. This may cause display or truncation issues in the UI.`); + if (warnings.length > 0) { + logger$3.warn(warnings); + } + const sortedActions = sortBy_1(finalValidActions, (a) => a.name.toLocaleLowerCase()); + return { + actions: sortedActions, + errors: errors, + }; + }; + + /** + * Copyright (c) 2023 + * + * Utilities related to reading configuration objects + * @summary Config-related utils + * @author Ayon Ghosh + */ + const urlRegex = new RegExp([ + '(^(https?:)//)?', + '(([^:/?#]*)(?::([0-9]+))?)', + '(/{0,1}[^?#]*)', + '(\\?[^#]*|)', + '(#.*|)$', // hash + ].join('')); + /** + * Parse and construct the ThoughtSpot hostname or IP address + * from the embed configuration object. + * @param config + */ + const getThoughtSpotHost = (config) => { + if (!config.thoughtSpotHost) { + throw new Error(ERROR_MESSAGE.INVALID_THOUGHTSPOT_HOST); + } + const urlParts = config.thoughtSpotHost.match(urlRegex); + if (!urlParts) { + throw new Error(ERROR_MESSAGE.INVALID_THOUGHTSPOT_HOST); + } + const protocol = urlParts[2] || window.location.protocol; + const host = urlParts[3]; + let path = urlParts[6]; + // Lose the trailing / if any + if (path.charAt(path.length - 1) === '/') { + path = path.substring(0, path.length - 1); + } + // const urlParams = urlParts[7]; + // const hash = urlParts[8]; + return `${protocol}//${host}${path}`; + }; + const getV2BasePath = (config) => { + if (config.basepath) { + return config.basepath; + } + const tsHost = getThoughtSpotHost(config); + // This is to handle when e2e's. Search is run on pods for + // comp-blink-test-pipeline with baseUrl=https://localhost:8443. + // This is to handle when the developer is developing in their local + // environment. + if (tsHost.includes('://localhost') && !tsHost.includes(':8443')) { + return ''; + } + return 'v2'; + }; + /** + * It is a good idea to keep URLs under 2000 chars. + * If this is ever breached, since we pass view configuration through + * URL params, we would like to log a warning. + * Reference: https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers + */ + const URL_MAX_LENGTH = 2000; + /** + * The default CSS dimensions of the embedded app + */ + const DEFAULT_EMBED_WIDTH = '100%'; + const DEFAULT_EMBED_HEIGHT = '100%'; + + var Config = { + DEBUG: false, + LIB_VERSION: '2.47.0' + }; + + // since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file + var window$1; + if (typeof(window) === 'undefined') { + var loc = { + hostname: '' + }; + window$1 = { + navigator: { userAgent: '' }, + document: { + location: loc, + referrer: '' + }, + screen: { width: 0, height: 0 }, + location: loc + }; + } else { + window$1 = window; + } + + /* + * Saved references to long variable names, so that closure compiler can + * minimize file size. + */ + + var ArrayProto = Array.prototype; + var FuncProto = Function.prototype; + var ObjProto = Object.prototype; + var slice = ArrayProto.slice; + var toString = ObjProto.toString; + var hasOwnProperty = ObjProto.hasOwnProperty; + var windowConsole = window$1.console; + var navigator = window$1.navigator; + var document$1 = window$1.document; + var windowOpera = window$1.opera; + var screen = window$1.screen; + var userAgent = navigator.userAgent; + var nativeBind = FuncProto.bind; + var nativeForEach = ArrayProto.forEach; + var nativeIndexOf = ArrayProto.indexOf; + var nativeMap = ArrayProto.map; + var nativeIsArray = Array.isArray; + var breaker = {}; + var _ = { + trim: function(str) { + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Polyfill + return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); + } + }; + + // Console override + var console$1 = { + /** @type {function(...*)} */ + log: function() { + if (Config.DEBUG && !_.isUndefined(windowConsole) && windowConsole) { + try { + windowConsole.log.apply(windowConsole, arguments); + } catch (err) { + _.each(arguments, function(arg) { + windowConsole.log(arg); + }); + } + } + }, + /** @type {function(...*)} */ + warn: function() { + if (Config.DEBUG && !_.isUndefined(windowConsole) && windowConsole) { + var args = ['Mixpanel warning:'].concat(_.toArray(arguments)); + try { + windowConsole.warn.apply(windowConsole, args); + } catch (err) { + _.each(args, function(arg) { + windowConsole.warn(arg); + }); + } + } + }, + /** @type {function(...*)} */ + error: function() { + if (Config.DEBUG && !_.isUndefined(windowConsole) && windowConsole) { + var args = ['Mixpanel error:'].concat(_.toArray(arguments)); + try { + windowConsole.error.apply(windowConsole, args); + } catch (err) { + _.each(args, function(arg) { + windowConsole.error(arg); + }); + } + } + }, + /** @type {function(...*)} */ + critical: function() { + if (!_.isUndefined(windowConsole) && windowConsole) { + var args = ['Mixpanel error:'].concat(_.toArray(arguments)); + try { + windowConsole.error.apply(windowConsole, args); + } catch (err) { + _.each(args, function(arg) { + windowConsole.error(arg); + }); + } + } + } + }; + + var log_func_with_prefix = function(func, prefix) { + return function() { + arguments[0] = '[' + prefix + '] ' + arguments[0]; + return func.apply(console$1, arguments); + }; + }; + var console_with_prefix = function(prefix) { + return { + log: log_func_with_prefix(console$1.log, prefix), + error: log_func_with_prefix(console$1.error, prefix), + critical: log_func_with_prefix(console$1.critical, prefix) + }; + }; + + + // UNDERSCORE + // Embed part of the Underscore Library + _.bind = function(func, context) { + var args, bound; + if (nativeBind && func.bind === nativeBind) { + return nativeBind.apply(func, slice.call(arguments, 1)); + } + if (!_.isFunction(func)) { + throw new TypeError(); + } + args = slice.call(arguments, 2); + bound = function() { + if (!(this instanceof bound)) { + return func.apply(context, args.concat(slice.call(arguments))); + } + var ctor = {}; + ctor.prototype = func.prototype; + var self = new ctor(); + ctor.prototype = null; + var result = func.apply(self, args.concat(slice.call(arguments))); + if (Object(result) === result) { + return result; + } + return self; + }; + return bound; + }; + + /** + * @param {*=} obj + * @param {function(...*)=} iterator + * @param {Object=} context + */ + _.each = function(obj, iterator, context) { + if (obj === null || obj === undefined) { + return; + } + if (nativeForEach && obj.forEach === nativeForEach) { + obj.forEach(iterator, context); + } else if (obj.length === +obj.length) { + for (var i = 0, l = obj.length; i < l; i++) { + if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) { + return; + } + } + } else { + for (var key in obj) { + if (hasOwnProperty.call(obj, key)) { + if (iterator.call(context, obj[key], key, obj) === breaker) { + return; + } + } + } + } + }; + + _.extend = function(obj) { + _.each(slice.call(arguments, 1), function(source) { + for (var prop in source) { + if (source[prop] !== void 0) { + obj[prop] = source[prop]; + } + } + }); + return obj; + }; + + _.isArray = nativeIsArray || function(obj) { + return toString.call(obj) === '[object Array]'; + }; + + // from a comment on http://dbj.org/dbj/?p=286 + // fails on only one very rare and deliberate custom object: + // var bomb = { toString : undefined, valueOf: function(o) { return "function BOMBA!"; }}; + _.isFunction = function(f) { + try { + return /^\s*\bfunction\b/.test(f); + } catch (x) { + return false; + } + }; + + _.isArguments = function(obj) { + return !!(obj && hasOwnProperty.call(obj, 'callee')); + }; + + _.toArray = function(iterable) { + if (!iterable) { + return []; + } + if (iterable.toArray) { + return iterable.toArray(); + } + if (_.isArray(iterable)) { + return slice.call(iterable); + } + if (_.isArguments(iterable)) { + return slice.call(iterable); + } + return _.values(iterable); + }; + + _.map = function(arr, callback, context) { + if (nativeMap && arr.map === nativeMap) { + return arr.map(callback, context); + } else { + var results = []; + _.each(arr, function(item) { + results.push(callback.call(context, item)); + }); + return results; + } + }; + + _.keys = function(obj) { + var results = []; + if (obj === null) { + return results; + } + _.each(obj, function(value, key) { + results[results.length] = key; + }); + return results; + }; + + _.values = function(obj) { + var results = []; + if (obj === null) { + return results; + } + _.each(obj, function(value) { + results[results.length] = value; + }); + return results; + }; + + _.include = function(obj, target) { + var found = false; + if (obj === null) { + return found; + } + if (nativeIndexOf && obj.indexOf === nativeIndexOf) { + return obj.indexOf(target) != -1; + } + _.each(obj, function(value) { + if (found || (found = (value === target))) { + return breaker; + } + }); + return found; + }; + + _.includes = function(str, needle) { + return str.indexOf(needle) !== -1; + }; + + // Underscore Addons + _.inherit = function(subclass, superclass) { + subclass.prototype = new superclass(); + subclass.prototype.constructor = subclass; + subclass.superclass = superclass.prototype; + return subclass; + }; + + _.isObject = function(obj) { + return (obj === Object(obj) && !_.isArray(obj)); + }; + + _.isEmptyObject = function(obj) { + if (_.isObject(obj)) { + for (var key in obj) { + if (hasOwnProperty.call(obj, key)) { + return false; + } + } + return true; + } + return false; + }; + + _.isUndefined = function(obj) { + return obj === void 0; + }; + + _.isString = function(obj) { + return toString.call(obj) == '[object String]'; + }; + + _.isDate = function(obj) { + return toString.call(obj) == '[object Date]'; + }; + + _.isNumber = function(obj) { + return toString.call(obj) == '[object Number]'; + }; + + _.isElement = function(obj) { + return !!(obj && obj.nodeType === 1); + }; + + _.encodeDates = function(obj) { + _.each(obj, function(v, k) { + if (_.isDate(v)) { + obj[k] = _.formatDate(v); + } else if (_.isObject(v)) { + obj[k] = _.encodeDates(v); // recurse + } + }); + return obj; + }; + + _.timestamp = function() { + Date.now = Date.now || function() { + return +new Date; + }; + return Date.now(); + }; + + _.formatDate = function(d) { + // YYYY-MM-DDTHH:MM:SS in UTC + function pad(n) { + return n < 10 ? '0' + n : n; + } + return d.getUTCFullYear() + '-' + + pad(d.getUTCMonth() + 1) + '-' + + pad(d.getUTCDate()) + 'T' + + pad(d.getUTCHours()) + ':' + + pad(d.getUTCMinutes()) + ':' + + pad(d.getUTCSeconds()); + }; + + _.strip_empty_properties = function(p) { + var ret = {}; + _.each(p, function(v, k) { + if (_.isString(v) && v.length > 0) { + ret[k] = v; + } + }); + return ret; + }; + + /* + * this function returns a copy of object after truncating it. If + * passed an Array or Object it will iterate through obj and + * truncate all the values recursively. + */ + _.truncate = function(obj, length) { + var ret; + + if (typeof(obj) === 'string') { + ret = obj.slice(0, length); + } else if (_.isArray(obj)) { + ret = []; + _.each(obj, function(val) { + ret.push(_.truncate(val, length)); + }); + } else if (_.isObject(obj)) { + ret = {}; + _.each(obj, function(val, key) { + ret[key] = _.truncate(val, length); + }); + } else { + ret = obj; + } + + return ret; + }; + + _.JSONEncode = (function() { + return function(mixed_val) { + var value = mixed_val; + var quote = function(string) { + var escapable = /[\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; // eslint-disable-line no-control-regex + var meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"': '\\"', + '\\': '\\\\' + }; + + escapable.lastIndex = 0; + return escapable.test(string) ? + '"' + string.replace(escapable, function(a) { + var c = meta[a]; + return typeof c === 'string' ? c : + '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : + '"' + string + '"'; + }; + + var str = function(key, holder) { + var gap = ''; + var indent = ' '; + var i = 0; // The loop counter. + var k = ''; // The member key. + var v = ''; // The member value. + var length = 0; + var mind = gap; + var partial = []; + var value = holder[key]; + + // If the value has a toJSON method, call it to obtain a replacement value. + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + + // What happens next depends on the value's type. + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + // JSON numbers must be finite. Encode non-finite numbers as null. + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + // If the value is a boolean or null, convert it to a string. Note: + // typeof null does not produce 'null'. The case is included here in + // the remote chance that this gets fixed someday. + + return String(value); + + case 'object': + // If the type is 'object', we might be dealing with an object or an array or + // null. + // Due to a specification blunder in ECMAScript, typeof null is 'object', + // so watch out for that case. + if (!value) { + return 'null'; + } + + // Make an array to hold the partial results of stringifying this object value. + gap += indent; + partial = []; + + // Is the value an array? + if (toString.apply(value) === '[object Array]') { + // The value is an array. Stringify every element. Use null as a placeholder + // for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + + // Join all of the elements together, separated with commas, and wrap them in + // brackets. + v = partial.length === 0 ? '[]' : + gap ? '[\n' + gap + + partial.join(',\n' + gap) + '\n' + + mind + ']' : + '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + + // Iterate through all of the keys in the object. + for (k in value) { + if (hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + + // Join all of the member texts together, separated with commas, + // and wrap them in braces. + v = partial.length === 0 ? '{}' : + gap ? '{' + partial.join(',') + '' + + mind + '}' : '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + }; + + // Make a fake root object containing our value under the key of ''. + // Return the result of stringifying the value. + return str('', { + '': value + }); + }; + })(); + + /** + * From https://github.com/douglascrockford/JSON-js/blob/master/json_parse.js + * Slightly modified to throw a real Error rather than a POJO + */ + _.JSONDecode = (function() { + var at, // The index of the current character + ch, // The current character + escapee = { + '"': '"', + '\\': '\\', + '/': '/', + 'b': '\b', + 'f': '\f', + 'n': '\n', + 'r': '\r', + 't': '\t' + }, + text, + error = function(m) { + var e = new SyntaxError(m); + e.at = at; + e.text = text; + throw e; + }, + next = function(c) { + // If a c parameter is provided, verify that it matches the current character. + if (c && c !== ch) { + error('Expected \'' + c + '\' instead of \'' + ch + '\''); + } + // Get the next character. When there are no more characters, + // return the empty string. + ch = text.charAt(at); + at += 1; + return ch; + }, + number = function() { + // Parse a number value. + var number, + string = ''; + + if (ch === '-') { + string = '-'; + next('-'); + } + while (ch >= '0' && ch <= '9') { + string += ch; + next(); + } + if (ch === '.') { + string += '.'; + while (next() && ch >= '0' && ch <= '9') { + string += ch; + } + } + if (ch === 'e' || ch === 'E') { + string += ch; + next(); + if (ch === '-' || ch === '+') { + string += ch; + next(); + } + while (ch >= '0' && ch <= '9') { + string += ch; + next(); + } + } + number = +string; + if (!isFinite(number)) { + error('Bad number'); + } else { + return number; + } + }, + + string = function() { + // Parse a string value. + var hex, + i, + string = '', + uffff; + // When parsing for string values, we must look for " and \ characters. + if (ch === '"') { + while (next()) { + if (ch === '"') { + next(); + return string; + } + if (ch === '\\') { + next(); + if (ch === 'u') { + uffff = 0; + for (i = 0; i < 4; i += 1) { + hex = parseInt(next(), 16); + if (!isFinite(hex)) { + break; + } + uffff = uffff * 16 + hex; + } + string += String.fromCharCode(uffff); + } else if (typeof escapee[ch] === 'string') { + string += escapee[ch]; + } else { + break; + } + } else { + string += ch; + } + } + } + error('Bad string'); + }, + white = function() { + // Skip whitespace. + while (ch && ch <= ' ') { + next(); + } + }, + word = function() { + // true, false, or null. + switch (ch) { + case 't': + next('t'); + next('r'); + next('u'); + next('e'); + return true; + case 'f': + next('f'); + next('a'); + next('l'); + next('s'); + next('e'); + return false; + case 'n': + next('n'); + next('u'); + next('l'); + next('l'); + return null; + } + error('Unexpected "' + ch + '"'); + }, + value, // Placeholder for the value function. + array = function() { + // Parse an array value. + var array = []; + + if (ch === '[') { + next('['); + white(); + if (ch === ']') { + next(']'); + return array; // empty array + } + while (ch) { + array.push(value()); + white(); + if (ch === ']') { + next(']'); + return array; + } + next(','); + white(); + } + } + error('Bad array'); + }, + object = function() { + // Parse an object value. + var key, + object = {}; + + if (ch === '{') { + next('{'); + white(); + if (ch === '}') { + next('}'); + return object; // empty object + } + while (ch) { + key = string(); + white(); + next(':'); + if (Object.hasOwnProperty.call(object, key)) { + error('Duplicate key "' + key + '"'); + } + object[key] = value(); + white(); + if (ch === '}') { + next('}'); + return object; + } + next(','); + white(); + } + } + error('Bad object'); + }; + + value = function() { + // Parse a JSON value. It could be an object, an array, a string, + // a number, or a word. + white(); + switch (ch) { + case '{': + return object(); + case '[': + return array(); + case '"': + return string(); + case '-': + return number(); + default: + return ch >= '0' && ch <= '9' ? number() : word(); + } + }; + + // Return the json_parse function. It will have access to all of the + // above functions and variables. + return function(source) { + var result; + + text = source; + at = 0; + ch = ' '; + result = value(); + white(); + if (ch) { + error('Syntax error'); + } + + return result; + }; + })(); + + _.base64Encode = function(data) { + var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, + ac = 0, + enc = '', + tmp_arr = []; + + if (!data) { + return data; + } + + data = _.utf8Encode(data); + + do { // pack three octets into four hexets + o1 = data.charCodeAt(i++); + o2 = data.charCodeAt(i++); + o3 = data.charCodeAt(i++); + + bits = o1 << 16 | o2 << 8 | o3; + + h1 = bits >> 18 & 0x3f; + h2 = bits >> 12 & 0x3f; + h3 = bits >> 6 & 0x3f; + h4 = bits & 0x3f; + + // use hexets to index into b64, and append result to encoded string + tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4); + } while (i < data.length); + + enc = tmp_arr.join(''); + + switch (data.length % 3) { + case 1: + enc = enc.slice(0, -2) + '=='; + break; + case 2: + enc = enc.slice(0, -1) + '='; + break; + } + + return enc; + }; + + _.utf8Encode = function(string) { + string = (string + '').replace(/\r\n/g, '\n').replace(/\r/g, '\n'); + + var utftext = '', + start, + end; + var stringl = 0, + n; + + start = end = 0; + stringl = string.length; + + for (n = 0; n < stringl; n++) { + var c1 = string.charCodeAt(n); + var enc = null; + + if (c1 < 128) { + end++; + } else if ((c1 > 127) && (c1 < 2048)) { + enc = String.fromCharCode((c1 >> 6) | 192, (c1 & 63) | 128); + } else { + enc = String.fromCharCode((c1 >> 12) | 224, ((c1 >> 6) & 63) | 128, (c1 & 63) | 128); + } + if (enc !== null) { + if (end > start) { + utftext += string.substring(start, end); + } + utftext += enc; + start = end = n + 1; + } + } + + if (end > start) { + utftext += string.substring(start, string.length); + } + + return utftext; + }; + + _.UUID = (function() { + + // Time-based entropy + var T = function() { + var time = 1 * new Date(); // cross-browser version of Date.now() + var ticks; + if (window$1.performance && window$1.performance.now) { + ticks = window$1.performance.now(); + } else { + // fall back to busy loop + ticks = 0; + + // this while loop figures how many browser ticks go by + // before 1*new Date() returns a new number, ie the amount + // of ticks that go by per millisecond + while (time == 1 * new Date()) { + ticks++; + } + } + return time.toString(16) + Math.floor(ticks).toString(16); + }; + + // Math.Random entropy + var R = function() { + return Math.random().toString(16).replace('.', ''); + }; + + // User agent entropy + // This function takes the user agent string, and then xors + // together each sequence of 8 bytes. This produces a final + // sequence of 8 bytes which it returns as hex. + var UA = function() { + var ua = userAgent, + i, ch, buffer = [], + ret = 0; + + function xor(result, byte_array) { + var j, tmp = 0; + for (j = 0; j < byte_array.length; j++) { + tmp |= (buffer[j] << j * 8); + } + return result ^ tmp; + } + + for (i = 0; i < ua.length; i++) { + ch = ua.charCodeAt(i); + buffer.unshift(ch & 0xFF); + if (buffer.length >= 4) { + ret = xor(ret, buffer); + buffer = []; + } + } + + if (buffer.length > 0) { + ret = xor(ret, buffer); + } + + return ret.toString(16); + }; + + return function() { + var se = (screen.height * screen.width).toString(16); + return (T() + '-' + R() + '-' + UA() + '-' + se + '-' + T()); + }; + })(); + + // _.isBlockedUA() + // This is to block various web spiders from executing our JS and + // sending false tracking data + var BLOCKED_UA_STRS = [ + 'ahrefsbot', + 'baiduspider', + 'bingbot', + 'bingpreview', + 'facebookexternal', + 'petalbot', + 'pinterest', + 'screaming frog', + 'yahoo! slurp', + 'yandexbot', + + // a whole bunch of goog-specific crawlers + // https://developers.google.com/search/docs/advanced/crawling/overview-google-crawlers + 'adsbot-google', + 'apis-google', + 'duplexweb-google', + 'feedfetcher-google', + 'google favicon', + 'google web preview', + 'google-read-aloud', + 'googlebot', + 'googleweblight', + 'mediapartners-google', + 'storebot-google' + ]; + _.isBlockedUA = function(ua) { + var i; + ua = ua.toLowerCase(); + for (i = 0; i < BLOCKED_UA_STRS.length; i++) { + if (ua.indexOf(BLOCKED_UA_STRS[i]) !== -1) { + return true; + } + } + return false; + }; + + /** + * @param {Object=} formdata + * @param {string=} arg_separator + */ + _.HTTPBuildQuery = function(formdata, arg_separator) { + var use_val, use_key, tmp_arr = []; + + if (_.isUndefined(arg_separator)) { + arg_separator = '&'; + } + + _.each(formdata, function(val, key) { + use_val = encodeURIComponent(val.toString()); + use_key = encodeURIComponent(key); + tmp_arr[tmp_arr.length] = use_key + '=' + use_val; + }); + + return tmp_arr.join(arg_separator); + }; + + _.getQueryParam = function(url, param) { + // Expects a raw URL + + param = param.replace(/[[]/, '\\[').replace(/[\]]/, '\\]'); + var regexS = '[\\?&]' + param + '=([^&#]*)', + regex = new RegExp(regexS), + results = regex.exec(url); + if (results === null || (results && typeof(results[1]) !== 'string' && results[1].length)) { + return ''; + } else { + var result = results[1]; + try { + result = decodeURIComponent(result); + } catch(err) { + console$1.error('Skipping decoding for malformed query param: ' + result); + } + return result.replace(/\+/g, ' '); + } + }; + + + // _.cookie + // Methods partially borrowed from quirksmode.org/js/cookies.html + _.cookie = { + get: function(name) { + var nameEQ = name + '='; + var ca = document$1.cookie.split(';'); + for (var i = 0; i < ca.length; i++) { + var c = ca[i]; + while (c.charAt(0) == ' ') { + c = c.substring(1, c.length); + } + if (c.indexOf(nameEQ) === 0) { + return decodeURIComponent(c.substring(nameEQ.length, c.length)); + } + } + return null; + }, + + parse: function(name) { + var cookie; + try { + cookie = _.JSONDecode(_.cookie.get(name)) || {}; + } catch (err) { + // noop + } + return cookie; + }, + + set_seconds: function(name, value, seconds, is_cross_subdomain, is_secure, is_cross_site, domain_override) { + var cdomain = '', + expires = '', + secure = ''; + + if (domain_override) { + cdomain = '; domain=' + domain_override; + } else if (is_cross_subdomain) { + var domain = extract_domain(document$1.location.hostname); + cdomain = domain ? '; domain=.' + domain : ''; + } + + if (seconds) { + var date = new Date(); + date.setTime(date.getTime() + (seconds * 1000)); + expires = '; expires=' + date.toGMTString(); + } + + if (is_cross_site) { + is_secure = true; + secure = '; SameSite=None'; + } + if (is_secure) { + secure += '; secure'; + } + + document$1.cookie = name + '=' + encodeURIComponent(value) + expires + '; path=/' + cdomain + secure; + }, + + set: function(name, value, days, is_cross_subdomain, is_secure, is_cross_site, domain_override) { + var cdomain = '', expires = '', secure = ''; + + if (domain_override) { + cdomain = '; domain=' + domain_override; + } else if (is_cross_subdomain) { + var domain = extract_domain(document$1.location.hostname); + cdomain = domain ? '; domain=.' + domain : ''; + } + + if (days) { + var date = new Date(); + date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + expires = '; expires=' + date.toGMTString(); + } + + if (is_cross_site) { + is_secure = true; + secure = '; SameSite=None'; + } + if (is_secure) { + secure += '; secure'; + } + + var new_cookie_val = name + '=' + encodeURIComponent(value) + expires + '; path=/' + cdomain + secure; + document$1.cookie = new_cookie_val; + return new_cookie_val; + }, + + remove: function(name, is_cross_subdomain, domain_override) { + _.cookie.set(name, '', -1, is_cross_subdomain, false, false, domain_override); + } + }; + + var _localStorageSupported = null; + var localStorageSupported = function(storage, forceCheck) { + if (_localStorageSupported !== null && !forceCheck) { + return _localStorageSupported; + } + + var supported = true; + try { + storage = storage || window.localStorage; + var key = '__mplss_' + cheap_guid(8), + val = 'xyz'; + storage.setItem(key, val); + if (storage.getItem(key) !== val) { + supported = false; + } + storage.removeItem(key); + } catch (err) { + supported = false; + } + + _localStorageSupported = supported; + return supported; + }; + + // _.localStorage + _.localStorage = { + is_supported: function(force_check) { + var supported = localStorageSupported(null, force_check); + if (!supported) { + console$1.error('localStorage unsupported; falling back to cookie store'); + } + return supported; + }, + + error: function(msg) { + console$1.error('localStorage error: ' + msg); + }, + + get: function(name) { + try { + return window.localStorage.getItem(name); + } catch (err) { + _.localStorage.error(err); + } + return null; + }, + + parse: function(name) { + try { + return _.JSONDecode(_.localStorage.get(name)) || {}; + } catch (err) { + // noop + } + return null; + }, + + set: function(name, value) { + try { + window.localStorage.setItem(name, value); + } catch (err) { + _.localStorage.error(err); + } + }, + + remove: function(name) { + try { + window.localStorage.removeItem(name); + } catch (err) { + _.localStorage.error(err); + } + } + }; + + _.register_event = (function() { + // written by Dean Edwards, 2005 + // with input from Tino Zijdel - crisp@xs4all.nl + // with input from Carl Sverre - mail@carlsverre.com + // with input from Mixpanel + // http://dean.edwards.name/weblog/2005/10/add-event/ + // https://gist.github.com/1930440 + + /** + * @param {Object} element + * @param {string} type + * @param {function(...*)} handler + * @param {boolean=} oldSchool + * @param {boolean=} useCapture + */ + var register_event = function(element, type, handler, oldSchool, useCapture) { + if (!element) { + console$1.error('No valid element provided to register_event'); + return; + } + + if (element.addEventListener && !oldSchool) { + element.addEventListener(type, handler, !!useCapture); + } else { + var ontype = 'on' + type; + var old_handler = element[ontype]; // can be undefined + element[ontype] = makeHandler(element, handler, old_handler); + } + }; + + function makeHandler(element, new_handler, old_handlers) { + var handler = function(event) { + event = event || fixEvent(window.event); + + // this basically happens in firefox whenever another script + // overwrites the onload callback and doesn't pass the event + // object to previously defined callbacks. All the browsers + // that don't define window.event implement addEventListener + // so the dom_loaded handler will still be fired as usual. + if (!event) { + return undefined; + } + + var ret = true; + var old_result, new_result; + + if (_.isFunction(old_handlers)) { + old_result = old_handlers(event); + } + new_result = new_handler.call(element, event); + + if ((false === old_result) || (false === new_result)) { + ret = false; + } + + return ret; + }; + + return handler; + } + + function fixEvent(event) { + if (event) { + event.preventDefault = fixEvent.preventDefault; + event.stopPropagation = fixEvent.stopPropagation; + } + return event; + } + fixEvent.preventDefault = function() { + this.returnValue = false; + }; + fixEvent.stopPropagation = function() { + this.cancelBubble = true; + }; + + return register_event; + })(); + + + var TOKEN_MATCH_REGEX = new RegExp('^(\\w*)\\[(\\w+)([=~\\|\\^\\$\\*]?)=?"?([^\\]"]*)"?\\]$'); + + _.dom_query = (function() { + /* document.getElementsBySelector(selector) + - returns an array of element objects from the current document + matching the CSS selector. Selectors can contain element names, + class names and ids and can be nested. For example: + + elements = document.getElementsBySelector('div#main p a.external') + + Will return an array of all 'a' elements with 'external' in their + class attribute that are contained inside 'p' elements that are + contained inside the 'div' element which has id="main" + + New in version 0.4: Support for CSS2 and CSS3 attribute selectors: + See http://www.w3.org/TR/css3-selectors/#attribute-selectors + + Version 0.4 - Simon Willison, March 25th 2003 + -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows + -- Opera 7 fails + + Version 0.5 - Carl Sverre, Jan 7th 2013 + -- Now uses jQuery-esque `hasClass` for testing class name + equality. This fixes a bug related to '-' characters being + considered not part of a 'word' in regex. + */ + + function getAllChildren(e) { + // Returns all children of element. Workaround required for IE5/Windows. Ugh. + return e.all ? e.all : e.getElementsByTagName('*'); + } + + var bad_whitespace = /[\t\r\n]/g; + + function hasClass(elem, selector) { + var className = ' ' + selector + ' '; + return ((' ' + elem.className + ' ').replace(bad_whitespace, ' ').indexOf(className) >= 0); + } + + function getElementsBySelector(selector) { + // Attempt to fail gracefully in lesser browsers + if (!document$1.getElementsByTagName) { + return []; + } + // Split selector in to tokens + var tokens = selector.split(' '); + var token, bits, tagName, found, foundCount, i, j, k, elements, currentContextIndex; + var currentContext = [document$1]; + for (i = 0; i < tokens.length; i++) { + token = tokens[i].replace(/^\s+/, '').replace(/\s+$/, ''); + if (token.indexOf('#') > -1) { + // Token is an ID selector + bits = token.split('#'); + tagName = bits[0]; + var id = bits[1]; + var element = document$1.getElementById(id); + if (!element || (tagName && element.nodeName.toLowerCase() != tagName)) { + // element not found or tag with that ID not found, return false + return []; + } + // Set currentContext to contain just this element + currentContext = [element]; + continue; // Skip to next token + } + if (token.indexOf('.') > -1) { + // Token contains a class selector + bits = token.split('.'); + tagName = bits[0]; + var className = bits[1]; + if (!tagName) { + tagName = '*'; + } + // Get elements matching tag, filter them for class selector + found = []; + foundCount = 0; + for (j = 0; j < currentContext.length; j++) { + if (tagName == '*') { + elements = getAllChildren(currentContext[j]); + } else { + elements = currentContext[j].getElementsByTagName(tagName); + } + for (k = 0; k < elements.length; k++) { + found[foundCount++] = elements[k]; + } + } + currentContext = []; + currentContextIndex = 0; + for (j = 0; j < found.length; j++) { + if (found[j].className && + _.isString(found[j].className) && // some SVG elements have classNames which are not strings + hasClass(found[j], className) + ) { + currentContext[currentContextIndex++] = found[j]; + } + } + continue; // Skip to next token + } + // Code to deal with attribute selectors + var token_match = token.match(TOKEN_MATCH_REGEX); + if (token_match) { + tagName = token_match[1]; + var attrName = token_match[2]; + var attrOperator = token_match[3]; + var attrValue = token_match[4]; + if (!tagName) { + tagName = '*'; + } + // Grab all of the tagName elements within current context + found = []; + foundCount = 0; + for (j = 0; j < currentContext.length; j++) { + if (tagName == '*') { + elements = getAllChildren(currentContext[j]); + } else { + elements = currentContext[j].getElementsByTagName(tagName); + } + for (k = 0; k < elements.length; k++) { + found[foundCount++] = elements[k]; + } + } + currentContext = []; + currentContextIndex = 0; + var checkFunction; // This function will be used to filter the elements + switch (attrOperator) { + case '=': // Equality + checkFunction = function(e) { + return (e.getAttribute(attrName) == attrValue); + }; + break; + case '~': // Match one of space seperated words + checkFunction = function(e) { + return (e.getAttribute(attrName).match(new RegExp('\\b' + attrValue + '\\b'))); + }; + break; + case '|': // Match start with value followed by optional hyphen + checkFunction = function(e) { + return (e.getAttribute(attrName).match(new RegExp('^' + attrValue + '-?'))); + }; + break; + case '^': // Match starts with value + checkFunction = function(e) { + return (e.getAttribute(attrName).indexOf(attrValue) === 0); + }; + break; + case '$': // Match ends with value - fails with "Warning" in Opera 7 + checkFunction = function(e) { + return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); + }; + break; + case '*': // Match ends with value + checkFunction = function(e) { + return (e.getAttribute(attrName).indexOf(attrValue) > -1); + }; + break; + default: + // Just test for existence of attribute + checkFunction = function(e) { + return e.getAttribute(attrName); + }; + } + currentContext = []; + currentContextIndex = 0; + for (j = 0; j < found.length; j++) { + if (checkFunction(found[j])) { + currentContext[currentContextIndex++] = found[j]; + } + } + // alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue); + continue; // Skip to next token + } + // If we get here, token is JUST an element (not a class or ID selector) + tagName = token; + found = []; + foundCount = 0; + for (j = 0; j < currentContext.length; j++) { + elements = currentContext[j].getElementsByTagName(tagName); + for (k = 0; k < elements.length; k++) { + found[foundCount++] = elements[k]; + } + } + currentContext = found; + } + return currentContext; + } + + return function(query) { + if (_.isElement(query)) { + return [query]; + } else if (_.isObject(query) && !_.isUndefined(query.length)) { + return query; + } else { + return getElementsBySelector.call(this, query); + } + }; + })(); + + var CAMPAIGN_KEYWORDS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term']; + var CLICK_IDS = ['dclid', 'fbclid', 'gclid', 'ko_click_id', 'li_fat_id', 'msclkid', 'ttclid', 'twclid', 'wbraid']; + + _.info = { + campaignParams: function(default_value) { + var kw = '', + params = {}; + _.each(CAMPAIGN_KEYWORDS, function(kwkey) { + kw = _.getQueryParam(document$1.URL, kwkey); + if (kw.length) { + params[kwkey] = kw; + } else if (default_value !== undefined) { + params[kwkey] = default_value; + } + }); + + return params; + }, + + clickParams: function() { + var id = '', + params = {}; + _.each(CLICK_IDS, function(idkey) { + id = _.getQueryParam(document$1.URL, idkey); + if (id.length) { + params[idkey] = id; + } + }); + + return params; + }, + + marketingParams: function() { + return _.extend(_.info.campaignParams(), _.info.clickParams()); + }, + + searchEngine: function(referrer) { + if (referrer.search('https?://(.*)google.([^/?]*)') === 0) { + return 'google'; + } else if (referrer.search('https?://(.*)bing.com') === 0) { + return 'bing'; + } else if (referrer.search('https?://(.*)yahoo.com') === 0) { + return 'yahoo'; + } else if (referrer.search('https?://(.*)duckduckgo.com') === 0) { + return 'duckduckgo'; + } else { + return null; + } + }, + + searchInfo: function(referrer) { + var search = _.info.searchEngine(referrer), + param = (search != 'yahoo') ? 'q' : 'p', + ret = {}; + + if (search !== null) { + ret['$search_engine'] = search; + + var keyword = _.getQueryParam(referrer, param); + if (keyword.length) { + ret['mp_keyword'] = keyword; + } + } + + return ret; + }, + + /** + * This function detects which browser is running this script. + * The order of the checks are important since many user agents + * include key words used in later checks. + */ + browser: function(user_agent, vendor, opera) { + vendor = vendor || ''; // vendor is undefined for at least IE9 + if (opera || _.includes(user_agent, ' OPR/')) { + if (_.includes(user_agent, 'Mini')) { + return 'Opera Mini'; + } + return 'Opera'; + } else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) { + return 'BlackBerry'; + } else if (_.includes(user_agent, 'IEMobile') || _.includes(user_agent, 'WPDesktop')) { + return 'Internet Explorer Mobile'; + } else if (_.includes(user_agent, 'SamsungBrowser/')) { + // https://developer.samsung.com/internet/user-agent-string-format + return 'Samsung Internet'; + } else if (_.includes(user_agent, 'Edge') || _.includes(user_agent, 'Edg/')) { + return 'Microsoft Edge'; + } else if (_.includes(user_agent, 'FBIOS')) { + return 'Facebook Mobile'; + } else if (_.includes(user_agent, 'Chrome')) { + return 'Chrome'; + } else if (_.includes(user_agent, 'CriOS')) { + return 'Chrome iOS'; + } else if (_.includes(user_agent, 'UCWEB') || _.includes(user_agent, 'UCBrowser')) { + return 'UC Browser'; + } else if (_.includes(user_agent, 'FxiOS')) { + return 'Firefox iOS'; + } else if (_.includes(vendor, 'Apple')) { + if (_.includes(user_agent, 'Mobile')) { + return 'Mobile Safari'; + } + return 'Safari'; + } else if (_.includes(user_agent, 'Android')) { + return 'Android Mobile'; + } else if (_.includes(user_agent, 'Konqueror')) { + return 'Konqueror'; + } else if (_.includes(user_agent, 'Firefox')) { + return 'Firefox'; + } else if (_.includes(user_agent, 'MSIE') || _.includes(user_agent, 'Trident/')) { + return 'Internet Explorer'; + } else if (_.includes(user_agent, 'Gecko')) { + return 'Mozilla'; + } else { + return ''; + } + }, + + /** + * This function detects which browser version is running this script, + * parsing major and minor version (e.g., 42.1). User agent strings from: + * http://www.useragentstring.com/pages/useragentstring.php + */ + browserVersion: function(userAgent, vendor, opera) { + var browser = _.info.browser(userAgent, vendor, opera); + var versionRegexs = { + 'Internet Explorer Mobile': /rv:(\d+(\.\d+)?)/, + 'Microsoft Edge': /Edge?\/(\d+(\.\d+)?)/, + 'Chrome': /Chrome\/(\d+(\.\d+)?)/, + 'Chrome iOS': /CriOS\/(\d+(\.\d+)?)/, + 'UC Browser' : /(UCBrowser|UCWEB)\/(\d+(\.\d+)?)/, + 'Safari': /Version\/(\d+(\.\d+)?)/, + 'Mobile Safari': /Version\/(\d+(\.\d+)?)/, + 'Opera': /(Opera|OPR)\/(\d+(\.\d+)?)/, + 'Firefox': /Firefox\/(\d+(\.\d+)?)/, + 'Firefox iOS': /FxiOS\/(\d+(\.\d+)?)/, + 'Konqueror': /Konqueror:(\d+(\.\d+)?)/, + 'BlackBerry': /BlackBerry (\d+(\.\d+)?)/, + 'Android Mobile': /android\s(\d+(\.\d+)?)/, + 'Samsung Internet': /SamsungBrowser\/(\d+(\.\d+)?)/, + 'Internet Explorer': /(rv:|MSIE )(\d+(\.\d+)?)/, + 'Mozilla': /rv:(\d+(\.\d+)?)/ + }; + var regex = versionRegexs[browser]; + if (regex === undefined) { + return null; + } + var matches = userAgent.match(regex); + if (!matches) { + return null; + } + return parseFloat(matches[matches.length - 2]); + }, + + os: function() { + var a = userAgent; + if (/Windows/i.test(a)) { + if (/Phone/.test(a) || /WPDesktop/.test(a)) { + return 'Windows Phone'; + } + return 'Windows'; + } else if (/(iPhone|iPad|iPod)/.test(a)) { + return 'iOS'; + } else if (/Android/.test(a)) { + return 'Android'; + } else if (/(BlackBerry|PlayBook|BB10)/i.test(a)) { + return 'BlackBerry'; + } else if (/Mac/i.test(a)) { + return 'Mac OS X'; + } else if (/Linux/.test(a)) { + return 'Linux'; + } else if (/CrOS/.test(a)) { + return 'Chrome OS'; + } else { + return ''; + } + }, + + device: function(user_agent) { + if (/Windows Phone/i.test(user_agent) || /WPDesktop/.test(user_agent)) { + return 'Windows Phone'; + } else if (/iPad/.test(user_agent)) { + return 'iPad'; + } else if (/iPod/.test(user_agent)) { + return 'iPod Touch'; + } else if (/iPhone/.test(user_agent)) { + return 'iPhone'; + } else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) { + return 'BlackBerry'; + } else if (/Android/.test(user_agent)) { + return 'Android'; + } else { + return ''; + } + }, + + referringDomain: function(referrer) { + var split = referrer.split('/'); + if (split.length >= 3) { + return split[2]; + } + return ''; + }, + + properties: function() { + return _.extend(_.strip_empty_properties({ + '$os': _.info.os(), + '$browser': _.info.browser(userAgent, navigator.vendor, windowOpera), + '$referrer': document$1.referrer, + '$referring_domain': _.info.referringDomain(document$1.referrer), + '$device': _.info.device(userAgent) + }), { + '$current_url': window$1.location.href, + '$browser_version': _.info.browserVersion(userAgent, navigator.vendor, windowOpera), + '$screen_height': screen.height, + '$screen_width': screen.width, + 'mp_lib': 'web', + '$lib_version': Config.LIB_VERSION, + '$insert_id': cheap_guid(), + 'time': _.timestamp() / 1000 // epoch time in seconds + }); + }, + + people_properties: function() { + return _.extend(_.strip_empty_properties({ + '$os': _.info.os(), + '$browser': _.info.browser(userAgent, navigator.vendor, windowOpera) + }), { + '$browser_version': _.info.browserVersion(userAgent, navigator.vendor, windowOpera) + }); + }, + + mpPageViewProperties: function() { + return _.strip_empty_properties({ + 'current_page_title': document$1.title, + 'current_domain': window$1.location.hostname, + 'current_url_path': window$1.location.pathname, + 'current_url_protocol': window$1.location.protocol, + 'current_url_search': window$1.location.search + }); + } + }; + + var cheap_guid = function(maxlen) { + var guid = Math.random().toString(36).substring(2, 10) + Math.random().toString(36).substring(2, 10); + return maxlen ? guid.substring(0, maxlen) : guid; + }; + + // naive way to extract domain name (example.com) from full hostname (my.sub.example.com) + var SIMPLE_DOMAIN_MATCH_REGEX = /[a-z0-9][a-z0-9-]*\.[a-z]+$/i; + // this next one attempts to account for some ccSLDs, e.g. extracting oxford.ac.uk from www.oxford.ac.uk + var DOMAIN_MATCH_REGEX = /[a-z0-9][a-z0-9-]+\.[a-z.]{2,6}$/i; + /** + * Attempts to extract main domain name from full hostname, using a few blunt heuristics. For + * common TLDs like .com/.org that always have a simple SLD.TLD structure (example.com), we + * simply extract the last two .-separated parts of the hostname (SIMPLE_DOMAIN_MATCH_REGEX). + * For others, we attempt to account for short ccSLD+TLD combos (.ac.uk) with the legacy + * DOMAIN_MATCH_REGEX (kept to maintain backwards compatibility with existing Mixpanel + * integrations). The only _reliable_ way to extract domain from hostname is with an up-to-date + * list like at https://publicsuffix.org/ so for cases that this helper fails at, the SDK + * offers the 'cookie_domain' config option to set it explicitly. + * @example + * extract_domain('my.sub.example.com') + * // 'example.com' + */ + var extract_domain = function(hostname) { + var domain_regex = DOMAIN_MATCH_REGEX; + var parts = hostname.split('.'); + var tld = parts[parts.length - 1]; + if (tld.length > 4 || tld === 'com' || tld === 'org') { + domain_regex = SIMPLE_DOMAIN_MATCH_REGEX; + } + var matches = hostname.match(domain_regex); + return matches ? matches[0] : ''; + }; + + var JSONStringify = null; + var JSONParse = null; + if (typeof JSON !== 'undefined') { + JSONStringify = JSON.stringify; + JSONParse = JSON.parse; + } + JSONStringify = JSONStringify || _.JSONEncode; + JSONParse = JSONParse || _.JSONDecode; + + // EXPORTS (for closure compiler) + _['toArray'] = _.toArray; + _['isObject'] = _.isObject; + _['JSONEncode'] = _.JSONEncode; + _['JSONDecode'] = _.JSONDecode; + _['isBlockedUA'] = _.isBlockedUA; + _['isEmptyObject'] = _.isEmptyObject; + _['info'] = _.info; + _['info']['device'] = _.info.device; + _['info']['browser'] = _.info.browser; + _['info']['browserVersion'] = _.info.browserVersion; + _['info']['properties'] = _.info.properties; + + /** + * DomTracker Object + * @constructor + */ + var DomTracker = function() {}; + + + // interface + DomTracker.prototype.create_properties = function() {}; + DomTracker.prototype.event_handler = function() {}; + DomTracker.prototype.after_track_handler = function() {}; + + DomTracker.prototype.init = function(mixpanel_instance) { + this.mp = mixpanel_instance; + return this; + }; + + /** + * @param {Object|string} query + * @param {string} event_name + * @param {Object=} properties + * @param {function=} user_callback + */ + DomTracker.prototype.track = function(query, event_name, properties, user_callback) { + var that = this; + var elements = _.dom_query(query); + + if (elements.length === 0) { + console$1.error('The DOM query (' + query + ') returned 0 elements'); + return; + } + + _.each(elements, function(element) { + _.register_event(element, this.override_event, function(e) { + var options = {}; + var props = that.create_properties(properties, this); + var timeout = that.mp.get_config('track_links_timeout'); + + that.event_handler(e, this, options); + + // in case the mixpanel servers don't get back to us in time + window.setTimeout(that.track_callback(user_callback, props, options, true), timeout); + + // fire the tracking event + that.mp.track(event_name, props, that.track_callback(user_callback, props, options)); + }); + }, this); + + return true; + }; + + /** + * @param {function} user_callback + * @param {Object} props + * @param {boolean=} timeout_occured + */ + DomTracker.prototype.track_callback = function(user_callback, props, options, timeout_occured) { + timeout_occured = timeout_occured || false; + var that = this; + + return function() { + // options is referenced from both callbacks, so we can have + // a 'lock' of sorts to ensure only one fires + if (options.callback_fired) { return; } + options.callback_fired = true; + + if (user_callback && user_callback(timeout_occured, props) === false) { + // user can prevent the default functionality by + // returning false from their callback + return; + } + + that.after_track_handler(props, options, timeout_occured); + }; + }; + + DomTracker.prototype.create_properties = function(properties, element) { + var props; + + if (typeof(properties) === 'function') { + props = properties(element); + } else { + props = _.extend({}, properties); + } + + return props; + }; + + /** + * LinkTracker Object + * @constructor + * @extends DomTracker + */ + var LinkTracker = function() { + this.override_event = 'click'; + }; + _.inherit(LinkTracker, DomTracker); + + LinkTracker.prototype.create_properties = function(properties, element) { + var props = LinkTracker.superclass.create_properties.apply(this, arguments); + + if (element.href) { props['url'] = element.href; } + + return props; + }; + + LinkTracker.prototype.event_handler = function(evt, element, options) { + options.new_tab = ( + evt.which === 2 || + evt.metaKey || + evt.ctrlKey || + element.target === '_blank' + ); + options.href = element.href; + + if (!options.new_tab) { + evt.preventDefault(); + } + }; + + LinkTracker.prototype.after_track_handler = function(props, options) { + if (options.new_tab) { return; } + + setTimeout(function() { + window.location = options.href; + }, 0); + }; + + /** + * FormTracker Object + * @constructor + * @extends DomTracker + */ + var FormTracker = function() { + this.override_event = 'submit'; + }; + _.inherit(FormTracker, DomTracker); + + FormTracker.prototype.event_handler = function(evt, element, options) { + options.element = element; + evt.preventDefault(); + }; + + FormTracker.prototype.after_track_handler = function(props, options) { + setTimeout(function() { + options.element.submit(); + }, 0); + }; + + // eslint-disable-line camelcase + + var logger$2 = console_with_prefix('lock'); + + /** + * SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser + * window/tab at a time will be able to access shared resources. + * + * Based on the Alur and Taubenfeld fast lock + * (http://www.cs.rochester.edu/research/synchronization/pseudocode/fastlock.html) + * with an added timeout to ensure there will be eventual progress in the event + * that a window is closed in the middle of the callback. + * + * Implementation based on the original version by David Wolever (https://github.com/wolever) + * at https://gist.github.com/wolever/5fd7573d1ef6166e8f8c4af286a69432. + * + * @example + * const myLock = new SharedLock('some-key'); + * myLock.withLock(function() { + * console.log('I hold the mutex!'); + * }); + * + * @constructor + */ + var SharedLock = function(key, options) { + options = options || {}; + + this.storageKey = key; + this.storage = options.storage || window.localStorage; + this.pollIntervalMS = options.pollIntervalMS || 100; + this.timeoutMS = options.timeoutMS || 2000; + }; + + // pass in a specific pid to test contention scenarios; otherwise + // it is chosen randomly for each acquisition attempt + SharedLock.prototype.withLock = function(lockedCB, errorCB, pid) { + if (!pid && typeof errorCB !== 'function') { + pid = errorCB; + errorCB = null; + } + + var i = pid || (new Date().getTime() + '|' + Math.random()); + var startTime = new Date().getTime(); + + var key = this.storageKey; + var pollIntervalMS = this.pollIntervalMS; + var timeoutMS = this.timeoutMS; + var storage = this.storage; + + var keyX = key + ':X'; + var keyY = key + ':Y'; + var keyZ = key + ':Z'; + + var reportError = function(err) { + errorCB && errorCB(err); + }; + + var delay = function(cb) { + if (new Date().getTime() - startTime > timeoutMS) { + logger$2.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']'); + storage.removeItem(keyZ); + storage.removeItem(keyY); + loop(); + return; + } + setTimeout(function() { + try { + cb(); + } catch(err) { + reportError(err); + } + }, pollIntervalMS * (Math.random() + 0.1)); + }; + + var waitFor = function(predicate, cb) { + if (predicate()) { + cb(); + } else { + delay(function() { + waitFor(predicate, cb); + }); + } + }; + + var getSetY = function() { + var valY = storage.getItem(keyY); + if (valY && valY !== i) { // if Y == i then this process already has the lock (useful for test cases) + return false; + } else { + storage.setItem(keyY, i); + if (storage.getItem(keyY) === i) { + return true; + } else { + if (!localStorageSupported(storage, true)) { + throw new Error('localStorage support dropped while acquiring lock'); + } + return false; + } + } + }; + + var loop = function() { + storage.setItem(keyX, i); + + waitFor(getSetY, function() { + if (storage.getItem(keyX) === i) { + criticalSection(); + return; + } + + delay(function() { + if (storage.getItem(keyY) !== i) { + loop(); + return; + } + waitFor(function() { + return !storage.getItem(keyZ); + }, criticalSection); + }); + }); + }; + + var criticalSection = function() { + storage.setItem(keyZ, '1'); + try { + lockedCB(); + } finally { + storage.removeItem(keyZ); + if (storage.getItem(keyY) === i) { + storage.removeItem(keyY); + } + if (storage.getItem(keyX) === i) { + storage.removeItem(keyX); + } + } + }; + + try { + if (localStorageSupported(storage, true)) { + loop(); + } else { + throw new Error('localStorage support check failed'); + } + } catch(err) { + reportError(err); + } + }; + + // eslint-disable-line camelcase + + var logger$1 = console_with_prefix('batch'); + + /** + * RequestQueue: queue for batching API requests with localStorage backup for retries. + * Maintains an in-memory queue which represents the source of truth for the current + * page, but also writes all items out to a copy in the browser's localStorage, which + * can be read on subsequent pageloads and retried. For batchability, all the request + * items in the queue should be of the same type (events, people updates, group updates) + * so they can be sent in a single request to the same API endpoint. + * + * LocalStorage keying and locking: In order for reloads and subsequent pageloads of + * the same site to access the same persisted data, they must share the same localStorage + * key (for instance based on project token and queue type). Therefore access to the + * localStorage entry is guarded by an asynchronous mutex (SharedLock) to prevent + * simultaneously open windows/tabs from overwriting each other's data (which would lead + * to data loss in some situations). + * @constructor + */ + var RequestQueue = function(storageKey, options) { + options = options || {}; + this.storageKey = storageKey; + this.storage = options.storage || window.localStorage; + this.reportError = options.errorReporter || _.bind(logger$1.error, logger$1); + this.lock = new SharedLock(storageKey, {storage: this.storage}); + + this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios + + this.memQueue = []; + }; + + /** + * Add one item to queues (memory and localStorage). The queued entry includes + * the given item along with an auto-generated ID and a "flush-after" timestamp. + * It is expected that the item will be sent over the network and dequeued + * before the flush-after time; if this doesn't happen it is considered orphaned + * (e.g., the original tab where it was enqueued got closed before it could be + * sent) and the item can be sent by any tab that finds it in localStorage. + * + * The final callback param is called with a param indicating success or + * failure of the enqueue operation; it is asynchronous because the localStorage + * lock is asynchronous. + */ + RequestQueue.prototype.enqueue = function(item, flushInterval, cb) { + var queueEntry = { + 'id': cheap_guid(), + 'flushAfter': new Date().getTime() + flushInterval * 2, + 'payload': item + }; + + this.lock.withLock(_.bind(function lockAcquired() { + var succeeded; + try { + var storedQueue = this.readFromStorage(); + storedQueue.push(queueEntry); + succeeded = this.saveToStorage(storedQueue); + if (succeeded) { + // only add to in-memory queue when storage succeeds + this.memQueue.push(queueEntry); + } + } catch(err) { + this.reportError('Error enqueueing item', item); + succeeded = false; + } + if (cb) { + cb(succeeded); + } + }, this), _.bind(function lockFailure(err) { + this.reportError('Error acquiring storage lock', err); + if (cb) { + cb(false); + } + }, this), this.pid); + }; + + /** + * Read out the given number of queue entries. If this.memQueue + * has fewer than batchSize items, then look for "orphaned" items + * in the persisted queue (items where the 'flushAfter' time has + * already passed). + */ + RequestQueue.prototype.fillBatch = function(batchSize) { + var batch = this.memQueue.slice(0, batchSize); + if (batch.length < batchSize) { + // don't need lock just to read events; localStorage is thread-safe + // and the worst that could happen is a duplicate send of some + // orphaned events, which will be deduplicated on the server side + var storedQueue = this.readFromStorage(); + if (storedQueue.length) { + // item IDs already in batch; don't duplicate out of storage + var idsInBatch = {}; // poor man's Set + _.each(batch, function(item) { idsInBatch[item['id']] = true; }); + + for (var i = 0; i < storedQueue.length; i++) { + var item = storedQueue[i]; + if (new Date().getTime() > item['flushAfter'] && !idsInBatch[item['id']]) { + item.orphaned = true; + batch.push(item); + if (batch.length >= batchSize) { + break; + } + } + } + } + } + return batch; + }; + + /** + * Remove items with matching 'id' from array (immutably) + * also remove any item without a valid id (e.g., malformed + * storage entries). + */ + var filterOutIDsAndInvalid = function(items, idSet) { + var filteredItems = []; + _.each(items, function(item) { + if (item['id'] && !idSet[item['id']]) { + filteredItems.push(item); + } + }); + return filteredItems; + }; + + /** + * Remove items with matching IDs from both in-memory queue + * and persisted queue + */ + RequestQueue.prototype.removeItemsByID = function(ids, cb) { + var idSet = {}; // poor man's Set + _.each(ids, function(id) { idSet[id] = true; }); + + this.memQueue = filterOutIDsAndInvalid(this.memQueue, idSet); + + var removeFromStorage = _.bind(function() { + var succeeded; + try { + var storedQueue = this.readFromStorage(); + storedQueue = filterOutIDsAndInvalid(storedQueue, idSet); + succeeded = this.saveToStorage(storedQueue); + + // an extra check: did storage report success but somehow + // the items are still there? + if (succeeded) { + storedQueue = this.readFromStorage(); + for (var i = 0; i < storedQueue.length; i++) { + var item = storedQueue[i]; + if (item['id'] && !!idSet[item['id']]) { + this.reportError('Item not removed from storage'); + return false; + } + } + } + } catch(err) { + this.reportError('Error removing items', ids); + succeeded = false; + } + return succeeded; + }, this); + + this.lock.withLock(function lockAcquired() { + var succeeded = removeFromStorage(); + if (cb) { + cb(succeeded); + } + }, _.bind(function lockFailure(err) { + var succeeded = false; + this.reportError('Error acquiring storage lock', err); + if (!localStorageSupported(this.storage, true)) { + // Looks like localStorage writes have stopped working sometime after + // initialization (probably full), and so nobody can acquire locks + // anymore. Consider it temporarily safe to remove items without the + // lock, since nobody's writing successfully anyway. + succeeded = removeFromStorage(); + if (!succeeded) { + // OK, we couldn't even write out the smaller queue. Try clearing it + // entirely. + try { + this.storage.removeItem(this.storageKey); + } catch(err) { + this.reportError('Error clearing queue', err); + } + } + } + if (cb) { + cb(succeeded); + } + }, this), this.pid); + }; + + // internal helper for RequestQueue.updatePayloads + var updatePayloads = function(existingItems, itemsToUpdate) { + var newItems = []; + _.each(existingItems, function(item) { + var id = item['id']; + if (id in itemsToUpdate) { + var newPayload = itemsToUpdate[id]; + if (newPayload !== null) { + item['payload'] = newPayload; + newItems.push(item); + } + } else { + // no update + newItems.push(item); + } + }); + return newItems; + }; + + /** + * Update payloads of given items in both in-memory queue and + * persisted queue. Items set to null are removed from queues. + */ + RequestQueue.prototype.updatePayloads = function(itemsToUpdate, cb) { + this.memQueue = updatePayloads(this.memQueue, itemsToUpdate); + this.lock.withLock(_.bind(function lockAcquired() { + var succeeded; + try { + var storedQueue = this.readFromStorage(); + storedQueue = updatePayloads(storedQueue, itemsToUpdate); + succeeded = this.saveToStorage(storedQueue); + } catch(err) { + this.reportError('Error updating items', itemsToUpdate); + succeeded = false; + } + if (cb) { + cb(succeeded); + } + }, this), _.bind(function lockFailure(err) { + this.reportError('Error acquiring storage lock', err); + if (cb) { + cb(false); + } + }, this), this.pid); + }; + + /** + * Read and parse items array from localStorage entry, handling + * malformed/missing data if necessary. + */ + RequestQueue.prototype.readFromStorage = function() { + var storageEntry; + try { + storageEntry = this.storage.getItem(this.storageKey); + if (storageEntry) { + storageEntry = JSONParse(storageEntry); + if (!_.isArray(storageEntry)) { + this.reportError('Invalid storage entry:', storageEntry); + storageEntry = null; + } + } + } catch (err) { + this.reportError('Error retrieving queue', err); + storageEntry = null; + } + return storageEntry || []; + }; + + /** + * Serialize the given items array to localStorage. + */ + RequestQueue.prototype.saveToStorage = function(queue) { + try { + this.storage.setItem(this.storageKey, JSONStringify(queue)); + return true; + } catch (err) { + this.reportError('Error saving queue', err); + return false; + } + }; + + /** + * Clear out queues (memory and localStorage). + */ + RequestQueue.prototype.clear = function() { + this.memQueue = []; + this.storage.removeItem(this.storageKey); + }; + + // eslint-disable-line camelcase + + // maximum interval between request retries after exponential backoff + var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes + + var logger = console_with_prefix('batch'); + + /** + * RequestBatcher: manages the queueing, flushing, retry etc of requests of one + * type (events, people, groups). + * Uses RequestQueue to manage the backing store. + * @constructor + */ + var RequestBatcher = function(storageKey, options) { + this.errorReporter = options.errorReporter; + this.queue = new RequestQueue(storageKey, { + errorReporter: _.bind(this.reportError, this), + storage: options.storage + }); + + this.libConfig = options.libConfig; + this.sendRequest = options.sendRequestFunc; + this.beforeSendHook = options.beforeSendHook; + this.stopAllBatching = options.stopAllBatchingFunc; + + // seed variable batch size + flush interval with configured values + this.batchSize = this.libConfig['batch_size']; + this.flushInterval = this.libConfig['batch_flush_interval_ms']; + + this.stopped = !this.libConfig['batch_autostart']; + this.consecutiveRemovalFailures = 0; + + // extra client-side dedupe + this.itemIdsSentSuccessfully = {}; + }; + + /** + * Add one item to queue. + */ + RequestBatcher.prototype.enqueue = function(item, cb) { + this.queue.enqueue(item, this.flushInterval, cb); + }; + + /** + * Start flushing batches at the configured time interval. Must call + * this method upon SDK init in order to send anything over the network. + */ + RequestBatcher.prototype.start = function() { + this.stopped = false; + this.consecutiveRemovalFailures = 0; + this.flush(); + }; + + /** + * Stop flushing batches. Can be restarted by calling start(). + */ + RequestBatcher.prototype.stop = function() { + this.stopped = true; + if (this.timeoutID) { + clearTimeout(this.timeoutID); + this.timeoutID = null; + } + }; + + /** + * Clear out queue. + */ + RequestBatcher.prototype.clear = function() { + this.queue.clear(); + }; + + /** + * Restore batch size configuration to whatever is set in the main SDK. + */ + RequestBatcher.prototype.resetBatchSize = function() { + this.batchSize = this.libConfig['batch_size']; + }; + + /** + * Restore flush interval time configuration to whatever is set in the main SDK. + */ + RequestBatcher.prototype.resetFlush = function() { + this.scheduleFlush(this.libConfig['batch_flush_interval_ms']); + }; + + /** + * Schedule the next flush in the given number of milliseconds. + */ + RequestBatcher.prototype.scheduleFlush = function(flushMS) { + this.flushInterval = flushMS; + if (!this.stopped) { // don't schedule anymore if batching has been stopped + this.timeoutID = setTimeout(_.bind(this.flush, this), this.flushInterval); + } + }; + + /** + * Flush one batch to network. Depending on success/failure modes, it will either + * remove the batch from the queue or leave it in for retry, and schedule the next + * flush. In cases of most network or API failures, it will back off exponentially + * when retrying. + * @param {Object} [options] + * @param {boolean} [options.sendBeacon] - whether to send batch with + * navigator.sendBeacon (only useful for sending batches before page unloads, as + * sendBeacon offers no callbacks or status indications) + */ + RequestBatcher.prototype.flush = function(options) { + try { + + if (this.requestInProgress) { + logger.log('Flush: Request already in progress'); + return; + } + + options = options || {}; + var timeoutMS = this.libConfig['batch_request_timeout_ms']; + var startTime = new Date().getTime(); + var currentBatchSize = this.batchSize; + var batch = this.queue.fillBatch(currentBatchSize); + var dataForRequest = []; + var transformedItems = {}; + _.each(batch, function(item) { + var payload = item['payload']; + if (this.beforeSendHook && !item.orphaned) { + payload = this.beforeSendHook(payload); + } + if (payload) { + // mp_sent_by_lib_version prop captures which lib version actually + // sends each event (regardless of which version originally queued + // it for sending) + if (payload['event'] && payload['properties']) { + payload['properties'] = _.extend( + {}, + payload['properties'], + {'mp_sent_by_lib_version': Config.LIB_VERSION} + ); + } + var addPayload = true; + var itemId = item['id']; + if (itemId) { + if ((this.itemIdsSentSuccessfully[itemId] || 0) > 5) { + this.reportError('[dupe] item ID sent too many times, not sending', { + item: item, + batchSize: batch.length, + timesSent: this.itemIdsSentSuccessfully[itemId] + }); + addPayload = false; + } + } else { + this.reportError('[dupe] found item with no ID', {item: item}); + } + + if (addPayload) { + dataForRequest.push(payload); + } + } + transformedItems[item['id']] = payload; + }, this); + if (dataForRequest.length < 1) { + this.resetFlush(); + return; // nothing to do + } + + this.requestInProgress = true; + + var batchSendCallback = _.bind(function(res) { + this.requestInProgress = false; + + try { + + // handle API response in a try-catch to make sure we can reset the + // flush operation if something goes wrong + + var removeItemsFromQueue = false; + if (options.unloading) { + // update persisted data to include hook transformations + this.queue.updatePayloads(transformedItems); + } else if ( + _.isObject(res) && + res.error === 'timeout' && + new Date().getTime() - startTime >= timeoutMS + ) { + this.reportError('Network timeout; retrying'); + this.flush(); + } else if ( + _.isObject(res) && + res.xhr_req && + (res.xhr_req['status'] >= 500 || res.xhr_req['status'] === 429 || res.error === 'timeout') + ) { + // network or API error, or 429 Too Many Requests, retry + var retryMS = this.flushInterval * 2; + var headers = res.xhr_req['responseHeaders']; + if (headers) { + var retryAfter = headers['Retry-After']; + if (retryAfter) { + retryMS = (parseInt(retryAfter, 10) * 1000) || retryMS; + } + } + retryMS = Math.min(MAX_RETRY_INTERVAL_MS, retryMS); + this.reportError('Error; retry in ' + retryMS + ' ms'); + this.scheduleFlush(retryMS); + } else if (_.isObject(res) && res.xhr_req && res.xhr_req['status'] === 413) { + // 413 Payload Too Large + if (batch.length > 1) { + var halvedBatchSize = Math.max(1, Math.floor(currentBatchSize / 2)); + this.batchSize = Math.min(this.batchSize, halvedBatchSize, batch.length - 1); + this.reportError('413 response; reducing batch size to ' + this.batchSize); + this.resetFlush(); + } else { + this.reportError('Single-event request too large; dropping', batch); + this.resetBatchSize(); + removeItemsFromQueue = true; + } + } else { + // successful network request+response; remove each item in batch from queue + // (even if it was e.g. a 400, in which case retrying won't help) + removeItemsFromQueue = true; + } + + if (removeItemsFromQueue) { + this.queue.removeItemsByID( + _.map(batch, function(item) { return item['id']; }), + _.bind(function(succeeded) { + if (succeeded) { + this.consecutiveRemovalFailures = 0; + this.flush(); // handle next batch if the queue isn't empty + } else { + this.reportError('Failed to remove items from queue'); + if (++this.consecutiveRemovalFailures > 5) { + this.reportError('Too many queue failures; disabling batching system.'); + this.stopAllBatching(); + } else { + this.resetFlush(); + } + } + }, this) + ); + + // client-side dedupe + _.each(batch, _.bind(function(item) { + var itemId = item['id']; + if (itemId) { + this.itemIdsSentSuccessfully[itemId] = this.itemIdsSentSuccessfully[itemId] || 0; + this.itemIdsSentSuccessfully[itemId]++; + if (this.itemIdsSentSuccessfully[itemId] > 5) { + this.reportError('[dupe] item ID sent too many times', { + item: item, + batchSize: batch.length, + timesSent: this.itemIdsSentSuccessfully[itemId] + }); + } + } else { + this.reportError('[dupe] found item with no ID while removing', {item: item}); + } + }, this)); + } + + } catch(err) { + this.reportError('Error handling API response', err); + this.resetFlush(); + } + }, this); + var requestOptions = { + method: 'POST', + verbose: true, + ignore_json_errors: true, // eslint-disable-line camelcase + timeout_ms: timeoutMS // eslint-disable-line camelcase + }; + if (options.unloading) { + requestOptions.transport = 'sendBeacon'; + } + logger.log('MIXPANEL REQUEST:', dataForRequest); + this.sendRequest(dataForRequest, requestOptions, batchSendCallback); + + } catch(err) { + this.reportError('Error flushing request queue', err); + this.resetFlush(); + } + }; + + /** + * Log error to global logger and optional user-defined logger. + */ + RequestBatcher.prototype.reportError = function(msg, err) { + logger.error.apply(logger.error, arguments); + if (this.errorReporter) { + try { + if (!(err instanceof Error)) { + err = new Error(msg); + } + this.errorReporter(msg, err); + } catch(err) { + logger.error(err); + } + } + }; + + /** + * A function used to track a Mixpanel event (e.g. MixpanelLib.track) + * @callback trackFunction + * @param {String} event_name The name of the event. This can be anything the user does - 'Button Click', 'Sign Up', 'Item Purchased', etc. + * @param {Object} [properties] A set of properties to include with the event you're sending. These describe the user who did the event or details about the event itself. + * @param {Function} [callback] If provided, the callback function will be called after tracking the event. + */ + + /** Public **/ + + var GDPR_DEFAULT_PERSISTENCE_PREFIX = '__mp_opt_in_out_'; + + /** + * Opt the user in to data tracking and cookies/localstorage for the given token + * @param {string} token - Mixpanel project tracking token + * @param {Object} [options] + * @param {trackFunction} [options.track] - function used for tracking a Mixpanel event to record the opt-in action + * @param {string} [options.trackEventName] - event name to be used for tracking the opt-in action + * @param {Object} [options.trackProperties] - set of properties to be tracked along with the opt-in action + * @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage + * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookieExpiration] - number of days until the opt-in cookie expires + * @param {string} [options.cookieDomain] - custom cookie domain + * @param {boolean} [options.crossSiteCookie] - whether the opt-in cookie is set as cross-site-enabled + * @param {boolean} [options.crossSubdomainCookie] - whether the opt-in cookie is set as cross-subdomain or not + * @param {boolean} [options.secureCookie] - whether the opt-in cookie is set as secure or not + */ + function optIn(token, options) { + _optInOut(true, token, options); + } + + /** + * Opt the user out of data tracking and cookies/localstorage for the given token + * @param {string} token - Mixpanel project tracking token + * @param {Object} [options] + * @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage + * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookieExpiration] - number of days until the opt-out cookie expires + * @param {string} [options.cookieDomain] - custom cookie domain + * @param {boolean} [options.crossSiteCookie] - whether the opt-in cookie is set as cross-site-enabled + * @param {boolean} [options.crossSubdomainCookie] - whether the opt-out cookie is set as cross-subdomain or not + * @param {boolean} [options.secureCookie] - whether the opt-out cookie is set as secure or not + */ + function optOut(token, options) { + _optInOut(false, token, options); + } + + /** + * Check whether the user has opted in to data tracking and cookies/localstorage for the given token + * @param {string} token - Mixpanel project tracking token + * @param {Object} [options] + * @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage + * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name + * @returns {boolean} whether the user has opted in to the given opt type + */ + function hasOptedIn(token, options) { + return _getStorageValue(token, options) === '1'; + } + + /** + * Check whether the user has opted out of data tracking and cookies/localstorage for the given token + * @param {string} token - Mixpanel project tracking token + * @param {Object} [options] + * @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage + * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name + * @param {boolean} [options.ignoreDnt] - flag to ignore browser DNT settings and always return false + * @returns {boolean} whether the user has opted out of the given opt type + */ + function hasOptedOut(token, options) { + if (_hasDoNotTrackFlagOn(options)) { + console$1.warn('This browser has "Do Not Track" enabled. This will prevent the Mixpanel SDK from sending any data. To ignore the "Do Not Track" browser setting, initialize the Mixpanel instance with the config "ignore_dnt: true"'); + return true; + } + var optedOut = _getStorageValue(token, options) === '0'; + if (optedOut) { + console$1.warn('You are opted out of Mixpanel tracking. This will prevent the Mixpanel SDK from sending any data.'); + } + return optedOut; + } + + /** + * Wrap a MixpanelLib method with a check for whether the user is opted out of data tracking and cookies/localstorage for the given token + * If the user has opted out, return early instead of executing the method. + * If a callback argument was provided, execute it passing the 0 error code. + * @param {function} method - wrapped method to be executed if the user has not opted out + * @returns {*} the result of executing method OR undefined if the user has opted out + */ + function addOptOutCheckMixpanelLib(method) { + return _addOptOutCheck(method, function(name) { + return this.get_config(name); + }); + } + + /** + * Wrap a MixpanelPeople method with a check for whether the user is opted out of data tracking and cookies/localstorage for the given token + * If the user has opted out, return early instead of executing the method. + * If a callback argument was provided, execute it passing the 0 error code. + * @param {function} method - wrapped method to be executed if the user has not opted out + * @returns {*} the result of executing method OR undefined if the user has opted out + */ + function addOptOutCheckMixpanelPeople(method) { + return _addOptOutCheck(method, function(name) { + return this._get_config(name); + }); + } + + /** + * Wrap a MixpanelGroup method with a check for whether the user is opted out of data tracking and cookies/localstorage for the given token + * If the user has opted out, return early instead of executing the method. + * If a callback argument was provided, execute it passing the 0 error code. + * @param {function} method - wrapped method to be executed if the user has not opted out + * @returns {*} the result of executing method OR undefined if the user has opted out + */ + function addOptOutCheckMixpanelGroup(method) { + return _addOptOutCheck(method, function(name) { + return this._get_config(name); + }); + } + + /** + * Clear the user's opt in/out status of data tracking and cookies/localstorage for the given token + * @param {string} token - Mixpanel project tracking token + * @param {Object} [options] + * @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage + * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookieExpiration] - number of days until the opt-in cookie expires + * @param {string} [options.cookieDomain] - custom cookie domain + * @param {boolean} [options.crossSiteCookie] - whether the opt-in cookie is set as cross-site-enabled + * @param {boolean} [options.crossSubdomainCookie] - whether the opt-in cookie is set as cross-subdomain or not + * @param {boolean} [options.secureCookie] - whether the opt-in cookie is set as secure or not + */ + function clearOptInOut(token, options) { + options = options || {}; + _getStorage(options).remove( + _getStorageKey(token, options), !!options.crossSubdomainCookie, options.cookieDomain + ); + } + + /** Private **/ + + /** + * Get storage util + * @param {Object} [options] + * @param {string} [options.persistenceType] + * @returns {object} either _.cookie or _.localstorage + */ + function _getStorage(options) { + options = options || {}; + return options.persistenceType === 'localStorage' ? _.localStorage : _.cookie; + } + + /** + * Get the name of the cookie that is used for the given opt type (tracking, cookie, etc.) + * @param {string} token - Mixpanel project tracking token + * @param {Object} [options] + * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name + * @returns {string} the name of the cookie for the given opt type + */ + function _getStorageKey(token, options) { + options = options || {}; + return (options.persistencePrefix || GDPR_DEFAULT_PERSISTENCE_PREFIX) + token; + } + + /** + * Get the value of the cookie that is used for the given opt type (tracking, cookie, etc.) + * @param {string} token - Mixpanel project tracking token + * @param {Object} [options] + * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name + * @returns {string} the value of the cookie for the given opt type + */ + function _getStorageValue(token, options) { + return _getStorage(options).get(_getStorageKey(token, options)); + } + + /** + * Check whether the user has set the DNT/doNotTrack setting to true in their browser + * @param {Object} [options] + * @param {string} [options.window] - alternate window object to check; used to force various DNT settings in browser tests + * @param {boolean} [options.ignoreDnt] - flag to ignore browser DNT settings and always return false + * @returns {boolean} whether the DNT setting is true + */ + function _hasDoNotTrackFlagOn(options) { + if (options && options.ignoreDnt) { + return false; + } + var win = (options && options.window) || window$1; + var nav = win['navigator'] || {}; + var hasDntOn = false; + + _.each([ + nav['doNotTrack'], // standard + nav['msDoNotTrack'], + win['doNotTrack'] + ], function(dntValue) { + if (_.includes([true, 1, '1', 'yes'], dntValue)) { + hasDntOn = true; + } + }); + + return hasDntOn; + } + + /** + * Set cookie/localstorage for the user indicating that they are opted in or out for the given opt type + * @param {boolean} optValue - whether to opt the user in or out for the given opt type + * @param {string} token - Mixpanel project tracking token + * @param {Object} [options] + * @param {trackFunction} [options.track] - function used for tracking a Mixpanel event to record the opt-in action + * @param {string} [options.trackEventName] - event name to be used for tracking the opt-in action + * @param {Object} [options.trackProperties] - set of properties to be tracked along with the opt-in action + * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookieExpiration] - number of days until the opt-in cookie expires + * @param {string} [options.cookieDomain] - custom cookie domain + * @param {boolean} [options.crossSiteCookie] - whether the opt-in cookie is set as cross-site-enabled + * @param {boolean} [options.crossSubdomainCookie] - whether the opt-in cookie is set as cross-subdomain or not + * @param {boolean} [options.secureCookie] - whether the opt-in cookie is set as secure or not + */ + function _optInOut(optValue, token, options) { + if (!_.isString(token) || !token.length) { + console$1.error('gdpr.' + (optValue ? 'optIn' : 'optOut') + ' called with an invalid token'); + return; + } + + options = options || {}; + + _getStorage(options).set( + _getStorageKey(token, options), + optValue ? 1 : 0, + _.isNumber(options.cookieExpiration) ? options.cookieExpiration : null, + !!options.crossSubdomainCookie, + !!options.secureCookie, + !!options.crossSiteCookie, + options.cookieDomain + ); + + if (options.track && optValue) { // only track event if opting in (optValue=true) + options.track(options.trackEventName || '$opt_in', options.trackProperties, { + 'send_immediately': true + }); + } + } + + /** + * Wrap a method with a check for whether the user is opted out of data tracking and cookies/localstorage for the given token + * If the user has opted out, return early instead of executing the method. + * If a callback argument was provided, execute it passing the 0 error code. + * @param {function} method - wrapped method to be executed if the user has not opted out + * @param {function} getConfigValue - getter function for the Mixpanel API token and other options to be used with opt-out check + * @returns {*} the result of executing method OR undefined if the user has opted out + */ + function _addOptOutCheck(method, getConfigValue) { + return function() { + var optedOut = false; + + try { + var token = getConfigValue.call(this, 'token'); + var ignoreDnt = getConfigValue.call(this, 'ignore_dnt'); + var persistenceType = getConfigValue.call(this, 'opt_out_tracking_persistence_type'); + var persistencePrefix = getConfigValue.call(this, 'opt_out_tracking_cookie_prefix'); + var win = getConfigValue.call(this, 'window'); // used to override window during browser tests + + if (token) { // if there was an issue getting the token, continue method execution as normal + optedOut = hasOptedOut(token, { + ignoreDnt: ignoreDnt, + persistenceType: persistenceType, + persistencePrefix: persistencePrefix, + window: win + }); + } + } catch(err) { + console$1.error('Unexpected error when checking tracking opt-out status: ' + err); + } + + if (!optedOut) { + return method.apply(this, arguments); + } + + var callback = arguments[arguments.length - 1]; + if (typeof(callback) === 'function') { + callback(0); + } + + return; + }; + } + + /** @const */ var SET_ACTION = '$set'; + /** @const */ var SET_ONCE_ACTION = '$set_once'; + /** @const */ var UNSET_ACTION = '$unset'; + /** @const */ var ADD_ACTION = '$add'; + /** @const */ var APPEND_ACTION = '$append'; + /** @const */ var UNION_ACTION = '$union'; + /** @const */ var REMOVE_ACTION = '$remove'; + /** @const */ var DELETE_ACTION = '$delete'; + + // Common internal methods for mixpanel.people and mixpanel.group APIs. + // These methods shouldn't involve network I/O. + var apiActions = { + set_action: function(prop, to) { + var data = {}; + var $set = {}; + if (_.isObject(prop)) { + _.each(prop, function(v, k) { + if (!this._is_reserved_property(k)) { + $set[k] = v; + } + }, this); + } else { + $set[prop] = to; + } + + data[SET_ACTION] = $set; + return data; + }, + + unset_action: function(prop) { + var data = {}; + var $unset = []; + if (!_.isArray(prop)) { + prop = [prop]; + } + + _.each(prop, function(k) { + if (!this._is_reserved_property(k)) { + $unset.push(k); + } + }, this); + + data[UNSET_ACTION] = $unset; + return data; + }, + + set_once_action: function(prop, to) { + var data = {}; + var $set_once = {}; + if (_.isObject(prop)) { + _.each(prop, function(v, k) { + if (!this._is_reserved_property(k)) { + $set_once[k] = v; + } + }, this); + } else { + $set_once[prop] = to; + } + data[SET_ONCE_ACTION] = $set_once; + return data; + }, + + union_action: function(list_name, values) { + var data = {}; + var $union = {}; + if (_.isObject(list_name)) { + _.each(list_name, function(v, k) { + if (!this._is_reserved_property(k)) { + $union[k] = _.isArray(v) ? v : [v]; + } + }, this); + } else { + $union[list_name] = _.isArray(values) ? values : [values]; + } + data[UNION_ACTION] = $union; + return data; + }, + + append_action: function(list_name, value) { + var data = {}; + var $append = {}; + if (_.isObject(list_name)) { + _.each(list_name, function(v, k) { + if (!this._is_reserved_property(k)) { + $append[k] = v; + } + }, this); + } else { + $append[list_name] = value; + } + data[APPEND_ACTION] = $append; + return data; + }, + + remove_action: function(list_name, value) { + var data = {}; + var $remove = {}; + if (_.isObject(list_name)) { + _.each(list_name, function(v, k) { + if (!this._is_reserved_property(k)) { + $remove[k] = v; + } + }, this); + } else { + $remove[list_name] = value; + } + data[REMOVE_ACTION] = $remove; + return data; + }, + + delete_action: function() { + var data = {}; + data[DELETE_ACTION] = ''; + return data; + } + }; + + /** + * Mixpanel Group Object + * @constructor + */ + var MixpanelGroup = function() {}; + + _.extend(MixpanelGroup.prototype, apiActions); + + MixpanelGroup.prototype._init = function(mixpanel_instance, group_key, group_id) { + this._mixpanel = mixpanel_instance; + this._group_key = group_key; + this._group_id = group_id; + }; + + /** + * Set properties on a group. + * + * ### Usage: + * + * mixpanel.get_group('company', 'mixpanel').set('Location', '405 Howard'); + * + * // or set multiple properties at once + * mixpanel.get_group('company', 'mixpanel').set({ + * 'Location': '405 Howard', + * 'Founded' : 2009, + * }); + * // properties can be strings, integers, dates, or lists + * + * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values. + * @param {*} [to] A value to set on the given property name + * @param {Function} [callback] If provided, the callback will be called after the tracking event + */ + MixpanelGroup.prototype.set = addOptOutCheckMixpanelGroup(function(prop, to, callback) { + var data = this.set_action(prop, to); + if (_.isObject(prop)) { + callback = to; + } + return this._send_request(data, callback); + }); + + /** + * Set properties on a group, only if they do not yet exist. + * This will not overwrite previous group property values, unlike + * group.set(). + * + * ### Usage: + * + * mixpanel.get_group('company', 'mixpanel').set_once('Location', '405 Howard'); + * + * // or set multiple properties at once + * mixpanel.get_group('company', 'mixpanel').set_once({ + * 'Location': '405 Howard', + * 'Founded' : 2009, + * }); + * // properties can be strings, integers, lists or dates + * + * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values. + * @param {*} [to] A value to set on the given property name + * @param {Function} [callback] If provided, the callback will be called after the tracking event + */ + MixpanelGroup.prototype.set_once = addOptOutCheckMixpanelGroup(function(prop, to, callback) { + var data = this.set_once_action(prop, to); + if (_.isObject(prop)) { + callback = to; + } + return this._send_request(data, callback); + }); + + /** + * Unset properties on a group permanently. + * + * ### Usage: + * + * mixpanel.get_group('company', 'mixpanel').unset('Founded'); + * + * @param {String} prop The name of the property. + * @param {Function} [callback] If provided, the callback will be called after the tracking event + */ + MixpanelGroup.prototype.unset = addOptOutCheckMixpanelGroup(function(prop, callback) { + var data = this.unset_action(prop); + return this._send_request(data, callback); + }); + + /** + * Merge a given list with a list-valued group property, excluding duplicate values. + * + * ### Usage: + * + * // merge a value to a list, creating it if needed + * mixpanel.get_group('company', 'mixpanel').union('Location', ['San Francisco', 'London']); + * + * @param {String} list_name Name of the property. + * @param {Array} values Values to merge with the given property + * @param {Function} [callback] If provided, the callback will be called after the tracking event + */ + MixpanelGroup.prototype.union = addOptOutCheckMixpanelGroup(function(list_name, values, callback) { + if (_.isObject(list_name)) { + callback = values; + } + var data = this.union_action(list_name, values); + return this._send_request(data, callback); + }); + + /** + * Permanently delete a group. + * + * ### Usage: + * + * mixpanel.get_group('company', 'mixpanel').delete(); + * + * @param {Function} [callback] If provided, the callback will be called after the tracking event + */ + MixpanelGroup.prototype['delete'] = addOptOutCheckMixpanelGroup(function(callback) { + // bracket notation above prevents a minification error related to reserved words + var data = this.delete_action(); + return this._send_request(data, callback); + }); + + /** + * Remove a property from a group. The value will be ignored if doesn't exist. + * + * ### Usage: + * + * mixpanel.get_group('company', 'mixpanel').remove('Location', 'London'); + * + * @param {String} list_name Name of the property. + * @param {Object} value Value to remove from the given group property + * @param {Function} [callback] If provided, the callback will be called after the tracking event + */ + MixpanelGroup.prototype.remove = addOptOutCheckMixpanelGroup(function(list_name, value, callback) { + var data = this.remove_action(list_name, value); + return this._send_request(data, callback); + }); + + MixpanelGroup.prototype._send_request = function(data, callback) { + data['$group_key'] = this._group_key; + data['$group_id'] = this._group_id; + data['$token'] = this._get_config('token'); + + var date_encoded_data = _.encodeDates(data); + return this._mixpanel._track_or_batch({ + type: 'groups', + data: date_encoded_data, + endpoint: this._get_config('api_host') + '/groups/', + batcher: this._mixpanel.request_batchers.groups + }, callback); + }; + + MixpanelGroup.prototype._is_reserved_property = function(prop) { + return prop === '$group_key' || prop === '$group_id'; + }; + + MixpanelGroup.prototype._get_config = function(conf) { + return this._mixpanel.get_config(conf); + }; + + MixpanelGroup.prototype.toString = function() { + return this._mixpanel.toString() + '.group.' + this._group_key + '.' + this._group_id; + }; + + // MixpanelGroup Exports + MixpanelGroup.prototype['remove'] = MixpanelGroup.prototype.remove; + MixpanelGroup.prototype['set'] = MixpanelGroup.prototype.set; + MixpanelGroup.prototype['set_once'] = MixpanelGroup.prototype.set_once; + MixpanelGroup.prototype['union'] = MixpanelGroup.prototype.union; + MixpanelGroup.prototype['unset'] = MixpanelGroup.prototype.unset; + MixpanelGroup.prototype['toString'] = MixpanelGroup.prototype.toString; + + /** + * Mixpanel People Object + * @constructor + */ + var MixpanelPeople = function() {}; + + _.extend(MixpanelPeople.prototype, apiActions); + + MixpanelPeople.prototype._init = function(mixpanel_instance) { + this._mixpanel = mixpanel_instance; + }; + + /* + * Set properties on a user record. + * + * ### Usage: + * + * mixpanel.people.set('gender', 'm'); + * + * // or set multiple properties at once + * mixpanel.people.set({ + * 'Company': 'Acme', + * 'Plan': 'Premium', + * 'Upgrade date': new Date() + * }); + * // properties can be strings, integers, dates, or lists + * + * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values. + * @param {*} [to] A value to set on the given property name + * @param {Function} [callback] If provided, the callback will be called after tracking the event. + */ + MixpanelPeople.prototype.set = addOptOutCheckMixpanelPeople(function(prop, to, callback) { + var data = this.set_action(prop, to); + if (_.isObject(prop)) { + callback = to; + } + // make sure that the referrer info has been updated and saved + if (this._get_config('save_referrer')) { + this._mixpanel['persistence'].update_referrer_info(document.referrer); + } + + // update $set object with default people properties + data[SET_ACTION] = _.extend( + {}, + _.info.people_properties(), + this._mixpanel['persistence'].get_referrer_info(), + data[SET_ACTION] + ); + return this._send_request(data, callback); + }); + + /* + * Set properties on a user record, only if they do not yet exist. + * This will not overwrite previous people property values, unlike + * people.set(). + * + * ### Usage: + * + * mixpanel.people.set_once('First Login Date', new Date()); + * + * // or set multiple properties at once + * mixpanel.people.set_once({ + * 'First Login Date': new Date(), + * 'Starting Plan': 'Premium' + * }); + * + * // properties can be strings, integers or dates + * + * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values. + * @param {*} [to] A value to set on the given property name + * @param {Function} [callback] If provided, the callback will be called after tracking the event. + */ + MixpanelPeople.prototype.set_once = addOptOutCheckMixpanelPeople(function(prop, to, callback) { + var data = this.set_once_action(prop, to); + if (_.isObject(prop)) { + callback = to; + } + return this._send_request(data, callback); + }); + + /* + * Unset properties on a user record (permanently removes the properties and their values from a profile). + * + * ### Usage: + * + * mixpanel.people.unset('gender'); + * + * // or unset multiple properties at once + * mixpanel.people.unset(['gender', 'Company']); + * + * @param {Array|String} prop If a string, this is the name of the property. If an array, this is a list of property names. + * @param {Function} [callback] If provided, the callback will be called after tracking the event. + */ + MixpanelPeople.prototype.unset = addOptOutCheckMixpanelPeople(function(prop, callback) { + var data = this.unset_action(prop); + return this._send_request(data, callback); + }); + + /* + * Increment/decrement numeric people analytics properties. + * + * ### Usage: + * + * mixpanel.people.increment('page_views', 1); + * + * // or, for convenience, if you're just incrementing a counter by + * // 1, you can simply do + * mixpanel.people.increment('page_views'); + * + * // to decrement a counter, pass a negative number + * mixpanel.people.increment('credits_left', -1); + * + * // like mixpanel.people.set(), you can increment multiple + * // properties at once: + * mixpanel.people.increment({ + * counter1: 1, + * counter2: 6 + * }); + * + * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and numeric values. + * @param {Number} [by] An amount to increment the given property + * @param {Function} [callback] If provided, the callback will be called after tracking the event. + */ + MixpanelPeople.prototype.increment = addOptOutCheckMixpanelPeople(function(prop, by, callback) { + var data = {}; + var $add = {}; + if (_.isObject(prop)) { + _.each(prop, function(v, k) { + if (!this._is_reserved_property(k)) { + if (isNaN(parseFloat(v))) { + console$1.error('Invalid increment value passed to mixpanel.people.increment - must be a number'); + return; + } else { + $add[k] = v; + } + } + }, this); + callback = by; + } else { + // convenience: mixpanel.people.increment('property'); will + // increment 'property' by 1 + if (_.isUndefined(by)) { + by = 1; + } + $add[prop] = by; + } + data[ADD_ACTION] = $add; + + return this._send_request(data, callback); + }); + + /* + * Append a value to a list-valued people analytics property. + * + * ### Usage: + * + * // append a value to a list, creating it if needed + * mixpanel.people.append('pages_visited', 'homepage'); + * + * // like mixpanel.people.set(), you can append multiple + * // properties at once: + * mixpanel.people.append({ + * list1: 'bob', + * list2: 123 + * }); + * + * @param {Object|String} list_name If a string, this is the name of the property. If an object, this is an associative array of names and values. + * @param {*} [value] value An item to append to the list + * @param {Function} [callback] If provided, the callback will be called after tracking the event. + */ + MixpanelPeople.prototype.append = addOptOutCheckMixpanelPeople(function(list_name, value, callback) { + if (_.isObject(list_name)) { + callback = value; + } + var data = this.append_action(list_name, value); + return this._send_request(data, callback); + }); + + /* + * Remove a value from a list-valued people analytics property. + * + * ### Usage: + * + * mixpanel.people.remove('School', 'UCB'); + * + * @param {Object|String} list_name If a string, this is the name of the property. If an object, this is an associative array of names and values. + * @param {*} [value] value Item to remove from the list + * @param {Function} [callback] If provided, the callback will be called after tracking the event. + */ + MixpanelPeople.prototype.remove = addOptOutCheckMixpanelPeople(function(list_name, value, callback) { + if (_.isObject(list_name)) { + callback = value; + } + var data = this.remove_action(list_name, value); + return this._send_request(data, callback); + }); + + /* + * Merge a given list with a list-valued people analytics property, + * excluding duplicate values. + * + * ### Usage: + * + * // merge a value to a list, creating it if needed + * mixpanel.people.union('pages_visited', 'homepage'); + * + * // like mixpanel.people.set(), you can append multiple + * // properties at once: + * mixpanel.people.union({ + * list1: 'bob', + * list2: 123 + * }); + * + * // like mixpanel.people.append(), you can append multiple + * // values to the same list: + * mixpanel.people.union({ + * list1: ['bob', 'billy'] + * }); + * + * @param {Object|String} list_name If a string, this is the name of the property. If an object, this is an associative array of names and values. + * @param {*} [value] Value / values to merge with the given property + * @param {Function} [callback] If provided, the callback will be called after tracking the event. + */ + MixpanelPeople.prototype.union = addOptOutCheckMixpanelPeople(function(list_name, values, callback) { + if (_.isObject(list_name)) { + callback = values; + } + var data = this.union_action(list_name, values); + return this._send_request(data, callback); + }); + + /* + * Record that you have charged the current user a certain amount + * of money. Charges recorded with track_charge() will appear in the + * Mixpanel revenue report. + * + * ### Usage: + * + * // charge a user $50 + * mixpanel.people.track_charge(50); + * + * // charge a user $30.50 on the 2nd of january + * mixpanel.people.track_charge(30.50, { + * '$time': new Date('jan 1 2012') + * }); + * + * @param {Number} amount The amount of money charged to the current user + * @param {Object} [properties] An associative array of properties associated with the charge + * @param {Function} [callback] If provided, the callback will be called when the server responds + * @deprecated + */ + MixpanelPeople.prototype.track_charge = addOptOutCheckMixpanelPeople(function(amount, properties, callback) { + if (!_.isNumber(amount)) { + amount = parseFloat(amount); + if (isNaN(amount)) { + console$1.error('Invalid value passed to mixpanel.people.track_charge - must be a number'); + return; + } + } + + return this.append('$transactions', _.extend({ + '$amount': amount + }, properties), callback); + }); + + /* + * Permanently clear all revenue report transactions from the + * current user's people analytics profile. + * + * ### Usage: + * + * mixpanel.people.clear_charges(); + * + * @param {Function} [callback] If provided, the callback will be called after tracking the event. + * @deprecated + */ + MixpanelPeople.prototype.clear_charges = function(callback) { + return this.set('$transactions', [], callback); + }; + + /* + * Permanently deletes the current people analytics profile from + * Mixpanel (using the current distinct_id). + * + * ### Usage: + * + * // remove the all data you have stored about the current user + * mixpanel.people.delete_user(); + * + */ + MixpanelPeople.prototype.delete_user = function() { + if (!this._identify_called()) { + console$1.error('mixpanel.people.delete_user() requires you to call identify() first'); + return; + } + var data = {'$delete': this._mixpanel.get_distinct_id()}; + return this._send_request(data); + }; + + MixpanelPeople.prototype.toString = function() { + return this._mixpanel.toString() + '.people'; + }; + + MixpanelPeople.prototype._send_request = function(data, callback) { + data['$token'] = this._get_config('token'); + data['$distinct_id'] = this._mixpanel.get_distinct_id(); + var device_id = this._mixpanel.get_property('$device_id'); + var user_id = this._mixpanel.get_property('$user_id'); + var had_persisted_distinct_id = this._mixpanel.get_property('$had_persisted_distinct_id'); + if (device_id) { + data['$device_id'] = device_id; + } + if (user_id) { + data['$user_id'] = user_id; + } + if (had_persisted_distinct_id) { + data['$had_persisted_distinct_id'] = had_persisted_distinct_id; + } + + var date_encoded_data = _.encodeDates(data); + + if (!this._identify_called()) { + this._enqueue(data); + if (!_.isUndefined(callback)) { + if (this._get_config('verbose')) { + callback({status: -1, error: null}); + } else { + callback(-1); + } + } + return _.truncate(date_encoded_data, 255); + } + + return this._mixpanel._track_or_batch({ + type: 'people', + data: date_encoded_data, + endpoint: this._get_config('api_host') + '/engage/', + batcher: this._mixpanel.request_batchers.people + }, callback); + }; + + MixpanelPeople.prototype._get_config = function(conf_var) { + return this._mixpanel.get_config(conf_var); + }; + + MixpanelPeople.prototype._identify_called = function() { + return this._mixpanel._flags.identify_called === true; + }; + + // Queue up engage operations if identify hasn't been called yet. + MixpanelPeople.prototype._enqueue = function(data) { + if (SET_ACTION in data) { + this._mixpanel['persistence']._add_to_people_queue(SET_ACTION, data); + } else if (SET_ONCE_ACTION in data) { + this._mixpanel['persistence']._add_to_people_queue(SET_ONCE_ACTION, data); + } else if (UNSET_ACTION in data) { + this._mixpanel['persistence']._add_to_people_queue(UNSET_ACTION, data); + } else if (ADD_ACTION in data) { + this._mixpanel['persistence']._add_to_people_queue(ADD_ACTION, data); + } else if (APPEND_ACTION in data) { + this._mixpanel['persistence']._add_to_people_queue(APPEND_ACTION, data); + } else if (REMOVE_ACTION in data) { + this._mixpanel['persistence']._add_to_people_queue(REMOVE_ACTION, data); + } else if (UNION_ACTION in data) { + this._mixpanel['persistence']._add_to_people_queue(UNION_ACTION, data); + } else { + console$1.error('Invalid call to _enqueue():', data); + } + }; + + MixpanelPeople.prototype._flush_one_queue = function(action, action_method, callback, queue_to_params_fn) { + var _this = this; + var queued_data = _.extend({}, this._mixpanel['persistence']._get_queue(action)); + var action_params = queued_data; + + if (!_.isUndefined(queued_data) && _.isObject(queued_data) && !_.isEmptyObject(queued_data)) { + _this._mixpanel['persistence']._pop_from_people_queue(action, queued_data); + if (queue_to_params_fn) { + action_params = queue_to_params_fn(queued_data); + } + action_method.call(_this, action_params, function(response, data) { + // on bad response, we want to add it back to the queue + if (response === 0) { + _this._mixpanel['persistence']._add_to_people_queue(action, queued_data); + } + if (!_.isUndefined(callback)) { + callback(response, data); + } + }); + } + }; + + // Flush queued engage operations - order does not matter, + // and there are network level race conditions anyway + MixpanelPeople.prototype._flush = function( + _set_callback, _add_callback, _append_callback, _set_once_callback, _union_callback, _unset_callback, _remove_callback + ) { + var _this = this; + var $append_queue = this._mixpanel['persistence']._get_queue(APPEND_ACTION); + var $remove_queue = this._mixpanel['persistence']._get_queue(REMOVE_ACTION); + + this._flush_one_queue(SET_ACTION, this.set, _set_callback); + this._flush_one_queue(SET_ONCE_ACTION, this.set_once, _set_once_callback); + this._flush_one_queue(UNSET_ACTION, this.unset, _unset_callback, function(queue) { return _.keys(queue); }); + this._flush_one_queue(ADD_ACTION, this.increment, _add_callback); + this._flush_one_queue(UNION_ACTION, this.union, _union_callback); + + // we have to fire off each $append individually since there is + // no concat method server side + if (!_.isUndefined($append_queue) && _.isArray($append_queue) && $append_queue.length) { + var $append_item; + var append_callback = function(response, data) { + if (response === 0) { + _this._mixpanel['persistence']._add_to_people_queue(APPEND_ACTION, $append_item); + } + if (!_.isUndefined(_append_callback)) { + _append_callback(response, data); + } + }; + for (var i = $append_queue.length - 1; i >= 0; i--) { + $append_item = $append_queue.pop(); + if (!_.isEmptyObject($append_item)) { + _this.append($append_item, append_callback); + } + } + // Save the shortened append queue + _this._mixpanel['persistence'].save(); + } + + // same for $remove + if (!_.isUndefined($remove_queue) && _.isArray($remove_queue) && $remove_queue.length) { + var $remove_item; + var remove_callback = function(response, data) { + if (response === 0) { + _this._mixpanel['persistence']._add_to_people_queue(REMOVE_ACTION, $remove_item); + } + if (!_.isUndefined(_remove_callback)) { + _remove_callback(response, data); + } + }; + for (var j = $remove_queue.length - 1; j >= 0; j--) { + $remove_item = $remove_queue.pop(); + if (!_.isEmptyObject($remove_item)) { + _this.remove($remove_item, remove_callback); + } + } + _this._mixpanel['persistence'].save(); + } + }; + + MixpanelPeople.prototype._is_reserved_property = function(prop) { + return prop === '$distinct_id' || prop === '$token' || prop === '$device_id' || prop === '$user_id' || prop === '$had_persisted_distinct_id'; + }; + + // MixpanelPeople Exports + MixpanelPeople.prototype['set'] = MixpanelPeople.prototype.set; + MixpanelPeople.prototype['set_once'] = MixpanelPeople.prototype.set_once; + MixpanelPeople.prototype['unset'] = MixpanelPeople.prototype.unset; + MixpanelPeople.prototype['increment'] = MixpanelPeople.prototype.increment; + MixpanelPeople.prototype['append'] = MixpanelPeople.prototype.append; + MixpanelPeople.prototype['remove'] = MixpanelPeople.prototype.remove; + MixpanelPeople.prototype['union'] = MixpanelPeople.prototype.union; + MixpanelPeople.prototype['track_charge'] = MixpanelPeople.prototype.track_charge; + MixpanelPeople.prototype['clear_charges'] = MixpanelPeople.prototype.clear_charges; + MixpanelPeople.prototype['delete_user'] = MixpanelPeople.prototype.delete_user; + MixpanelPeople.prototype['toString'] = MixpanelPeople.prototype.toString; + + /* + * Constants + */ + /** @const */ var SET_QUEUE_KEY = '__mps'; + /** @const */ var SET_ONCE_QUEUE_KEY = '__mpso'; + /** @const */ var UNSET_QUEUE_KEY = '__mpus'; + /** @const */ var ADD_QUEUE_KEY = '__mpa'; + /** @const */ var APPEND_QUEUE_KEY = '__mpap'; + /** @const */ var REMOVE_QUEUE_KEY = '__mpr'; + /** @const */ var UNION_QUEUE_KEY = '__mpu'; + // This key is deprecated, but we want to check for it to see whether aliasing is allowed. + /** @const */ var PEOPLE_DISTINCT_ID_KEY = '$people_distinct_id'; + /** @const */ var ALIAS_ID_KEY = '__alias'; + /** @const */ var EVENT_TIMERS_KEY = '__timers'; + /** @const */ var RESERVED_PROPERTIES = [ + SET_QUEUE_KEY, + SET_ONCE_QUEUE_KEY, + UNSET_QUEUE_KEY, + ADD_QUEUE_KEY, + APPEND_QUEUE_KEY, + REMOVE_QUEUE_KEY, + UNION_QUEUE_KEY, + PEOPLE_DISTINCT_ID_KEY, + ALIAS_ID_KEY, + EVENT_TIMERS_KEY + ]; + + /** + * Mixpanel Persistence Object + * @constructor + */ + var MixpanelPersistence = function(config) { + this['props'] = {}; + this.campaign_params_saved = false; + + if (config['persistence_name']) { + this.name = 'mp_' + config['persistence_name']; + } else { + this.name = 'mp_' + config['token'] + '_mixpanel'; + } + + var storage_type = config['persistence']; + if (storage_type !== 'cookie' && storage_type !== 'localStorage') { + console$1.critical('Unknown persistence type ' + storage_type + '; falling back to cookie'); + storage_type = config['persistence'] = 'cookie'; + } + + if (storage_type === 'localStorage' && _.localStorage.is_supported()) { + this.storage = _.localStorage; + } else { + this.storage = _.cookie; + } + + this.load(); + this.update_config(config); + this.upgrade(config); + this.save(); + }; + + MixpanelPersistence.prototype.properties = function() { + var p = {}; + // Filter out reserved properties + _.each(this['props'], function(v, k) { + if (!_.include(RESERVED_PROPERTIES, k)) { + p[k] = v; + } + }); + return p; + }; + + MixpanelPersistence.prototype.load = function() { + if (this.disabled) { return; } + + var entry = this.storage.parse(this.name); + + if (entry) { + this['props'] = _.extend({}, entry); + } + }; + + MixpanelPersistence.prototype.upgrade = function(config) { + var upgrade_from_old_lib = config['upgrade'], + old_cookie_name, + old_cookie; + + if (upgrade_from_old_lib) { + old_cookie_name = 'mp_super_properties'; + // Case where they had a custom cookie name before. + if (typeof(upgrade_from_old_lib) === 'string') { + old_cookie_name = upgrade_from_old_lib; + } + + old_cookie = this.storage.parse(old_cookie_name); + + // remove the cookie + this.storage.remove(old_cookie_name); + this.storage.remove(old_cookie_name, true); + + if (old_cookie) { + this['props'] = _.extend( + this['props'], + old_cookie['all'], + old_cookie['events'] + ); + } + } + + if (!config['cookie_name'] && config['name'] !== 'mixpanel') { + // special case to handle people with cookies of the form + // mp_TOKEN_INSTANCENAME from the first release of this library + old_cookie_name = 'mp_' + config['token'] + '_' + config['name']; + old_cookie = this.storage.parse(old_cookie_name); + + if (old_cookie) { + this.storage.remove(old_cookie_name); + this.storage.remove(old_cookie_name, true); + + // Save the prop values that were in the cookie from before - + // this should only happen once as we delete the old one. + this.register_once(old_cookie); + } + } + + if (this.storage === _.localStorage) { + old_cookie = _.cookie.parse(this.name); + + _.cookie.remove(this.name); + _.cookie.remove(this.name, true); + + if (old_cookie) { + this.register_once(old_cookie); + } + } + }; + + MixpanelPersistence.prototype.save = function() { + if (this.disabled) { return; } + this.storage.set( + this.name, + _.JSONEncode(this['props']), + this.expire_days, + this.cross_subdomain, + this.secure, + this.cross_site, + this.cookie_domain + ); + }; + + MixpanelPersistence.prototype.remove = function() { + // remove both domain and subdomain cookies + this.storage.remove(this.name, false, this.cookie_domain); + this.storage.remove(this.name, true, this.cookie_domain); + }; + + // removes the storage entry and deletes all loaded data + // forced name for tests + MixpanelPersistence.prototype.clear = function() { + this.remove(); + this['props'] = {}; + }; + + /** + * @param {Object} props + * @param {*=} default_value + * @param {number=} days + */ + MixpanelPersistence.prototype.register_once = function(props, default_value, days) { + if (_.isObject(props)) { + if (typeof(default_value) === 'undefined') { default_value = 'None'; } + this.expire_days = (typeof(days) === 'undefined') ? this.default_expiry : days; + + _.each(props, function(val, prop) { + if (!this['props'].hasOwnProperty(prop) || this['props'][prop] === default_value) { + this['props'][prop] = val; + } + }, this); + + this.save(); + + return true; + } + return false; + }; + + /** + * @param {Object} props + * @param {number=} days + */ + MixpanelPersistence.prototype.register = function(props, days) { + if (_.isObject(props)) { + this.expire_days = (typeof(days) === 'undefined') ? this.default_expiry : days; + + _.extend(this['props'], props); + + this.save(); + + return true; + } + return false; + }; + + MixpanelPersistence.prototype.unregister = function(prop) { + if (prop in this['props']) { + delete this['props'][prop]; + this.save(); + } + }; + + MixpanelPersistence.prototype.update_search_keyword = function(referrer) { + this.register(_.info.searchInfo(referrer)); + }; + + // EXPORTED METHOD, we test this directly. + MixpanelPersistence.prototype.update_referrer_info = function(referrer) { + // If referrer doesn't exist, we want to note the fact that it was type-in traffic. + this.register_once({ + '$initial_referrer': referrer || '$direct', + '$initial_referring_domain': _.info.referringDomain(referrer) || '$direct' + }, ''); + }; + + MixpanelPersistence.prototype.get_referrer_info = function() { + return _.strip_empty_properties({ + '$initial_referrer': this['props']['$initial_referrer'], + '$initial_referring_domain': this['props']['$initial_referring_domain'] + }); + }; + + // safely fills the passed in object with stored properties, + // does not override any properties defined in both + // returns the passed in object + MixpanelPersistence.prototype.safe_merge = function(props) { + _.each(this['props'], function(val, prop) { + if (!(prop in props)) { + props[prop] = val; + } + }); + + return props; + }; + + MixpanelPersistence.prototype.update_config = function(config) { + this.default_expiry = this.expire_days = config['cookie_expiration']; + this.set_disabled(config['disable_persistence']); + this.set_cookie_domain(config['cookie_domain']); + this.set_cross_site(config['cross_site_cookie']); + this.set_cross_subdomain(config['cross_subdomain_cookie']); + this.set_secure(config['secure_cookie']); + }; + + MixpanelPersistence.prototype.set_disabled = function(disabled) { + this.disabled = disabled; + if (this.disabled) { + this.remove(); + } else { + this.save(); + } + }; + + MixpanelPersistence.prototype.set_cookie_domain = function(cookie_domain) { + if (cookie_domain !== this.cookie_domain) { + this.remove(); + this.cookie_domain = cookie_domain; + this.save(); + } + }; + + MixpanelPersistence.prototype.set_cross_site = function(cross_site) { + if (cross_site !== this.cross_site) { + this.cross_site = cross_site; + this.remove(); + this.save(); + } + }; + + MixpanelPersistence.prototype.set_cross_subdomain = function(cross_subdomain) { + if (cross_subdomain !== this.cross_subdomain) { + this.cross_subdomain = cross_subdomain; + this.remove(); + this.save(); + } + }; + + MixpanelPersistence.prototype.get_cross_subdomain = function() { + return this.cross_subdomain; + }; + + MixpanelPersistence.prototype.set_secure = function(secure) { + if (secure !== this.secure) { + this.secure = secure ? true : false; + this.remove(); + this.save(); + } + }; + + MixpanelPersistence.prototype._add_to_people_queue = function(queue, data) { + var q_key = this._get_queue_key(queue), + q_data = data[queue], + set_q = this._get_or_create_queue(SET_ACTION), + set_once_q = this._get_or_create_queue(SET_ONCE_ACTION), + unset_q = this._get_or_create_queue(UNSET_ACTION), + add_q = this._get_or_create_queue(ADD_ACTION), + union_q = this._get_or_create_queue(UNION_ACTION), + remove_q = this._get_or_create_queue(REMOVE_ACTION, []), + append_q = this._get_or_create_queue(APPEND_ACTION, []); + + if (q_key === SET_QUEUE_KEY) { + // Update the set queue - we can override any existing values + _.extend(set_q, q_data); + // if there was a pending increment, override it + // with the set. + this._pop_from_people_queue(ADD_ACTION, q_data); + // if there was a pending union, override it + // with the set. + this._pop_from_people_queue(UNION_ACTION, q_data); + this._pop_from_people_queue(UNSET_ACTION, q_data); + } else if (q_key === SET_ONCE_QUEUE_KEY) { + // only queue the data if there is not already a set_once call for it. + _.each(q_data, function(v, k) { + if (!(k in set_once_q)) { + set_once_q[k] = v; + } + }); + this._pop_from_people_queue(UNSET_ACTION, q_data); + } else if (q_key === UNSET_QUEUE_KEY) { + _.each(q_data, function(prop) { + + // undo previously-queued actions on this key + _.each([set_q, set_once_q, add_q, union_q], function(enqueued_obj) { + if (prop in enqueued_obj) { + delete enqueued_obj[prop]; + } + }); + _.each(append_q, function(append_obj) { + if (prop in append_obj) { + delete append_obj[prop]; + } + }); + + unset_q[prop] = true; + + }); + } else if (q_key === ADD_QUEUE_KEY) { + _.each(q_data, function(v, k) { + // If it exists in the set queue, increment + // the value + if (k in set_q) { + set_q[k] += v; + } else { + // If it doesn't exist, update the add + // queue + if (!(k in add_q)) { + add_q[k] = 0; + } + add_q[k] += v; + } + }, this); + this._pop_from_people_queue(UNSET_ACTION, q_data); + } else if (q_key === UNION_QUEUE_KEY) { + _.each(q_data, function(v, k) { + if (_.isArray(v)) { + if (!(k in union_q)) { + union_q[k] = []; + } + // We may send duplicates, the server will dedup them. + union_q[k] = union_q[k].concat(v); + } + }); + this._pop_from_people_queue(UNSET_ACTION, q_data); + } else if (q_key === REMOVE_QUEUE_KEY) { + remove_q.push(q_data); + this._pop_from_people_queue(APPEND_ACTION, q_data); + } else if (q_key === APPEND_QUEUE_KEY) { + append_q.push(q_data); + this._pop_from_people_queue(UNSET_ACTION, q_data); + } + + console$1.log('MIXPANEL PEOPLE REQUEST (QUEUED, PENDING IDENTIFY):'); + console$1.log(data); + + this.save(); + }; + + MixpanelPersistence.prototype._pop_from_people_queue = function(queue, data) { + var q = this._get_queue(queue); + if (!_.isUndefined(q)) { + _.each(data, function(v, k) { + if (queue === APPEND_ACTION || queue === REMOVE_ACTION) { + // list actions: only remove if both k+v match + // e.g. remove should not override append in a case like + // append({foo: 'bar'}); remove({foo: 'qux'}) + _.each(q, function(queued_action) { + if (queued_action[k] === v) { + delete queued_action[k]; + } + }); + } else { + delete q[k]; + } + }, this); + + this.save(); + } + }; + + MixpanelPersistence.prototype._get_queue_key = function(queue) { + if (queue === SET_ACTION) { + return SET_QUEUE_KEY; + } else if (queue === SET_ONCE_ACTION) { + return SET_ONCE_QUEUE_KEY; + } else if (queue === UNSET_ACTION) { + return UNSET_QUEUE_KEY; + } else if (queue === ADD_ACTION) { + return ADD_QUEUE_KEY; + } else if (queue === APPEND_ACTION) { + return APPEND_QUEUE_KEY; + } else if (queue === REMOVE_ACTION) { + return REMOVE_QUEUE_KEY; + } else if (queue === UNION_ACTION) { + return UNION_QUEUE_KEY; + } else { + console$1.error('Invalid queue:', queue); + } + }; + + MixpanelPersistence.prototype._get_queue = function(queue) { + return this['props'][this._get_queue_key(queue)]; + }; + MixpanelPersistence.prototype._get_or_create_queue = function(queue, default_val) { + var key = this._get_queue_key(queue); + default_val = _.isUndefined(default_val) ? {} : default_val; + + return this['props'][key] || (this['props'][key] = default_val); + }; + + MixpanelPersistence.prototype.set_event_timer = function(event_name, timestamp) { + var timers = this['props'][EVENT_TIMERS_KEY] || {}; + timers[event_name] = timestamp; + this['props'][EVENT_TIMERS_KEY] = timers; + this.save(); + }; + + MixpanelPersistence.prototype.remove_event_timer = function(event_name) { + var timers = this['props'][EVENT_TIMERS_KEY] || {}; + var timestamp = timers[event_name]; + if (!_.isUndefined(timestamp)) { + delete this['props'][EVENT_TIMERS_KEY][event_name]; + this.save(); + } + return timestamp; + }; + + /* + * Mixpanel JS Library + * + * Copyright 2012, Mixpanel, Inc. All Rights Reserved + * http://mixpanel.com/ + * + * Includes portions of Underscore.js + * http://documentcloud.github.com/underscore/ + * (c) 2011 Jeremy Ashkenas, DocumentCloud Inc. + * Released under the MIT License. + */ + + // ==ClosureCompiler== + // @compilation_level ADVANCED_OPTIMIZATIONS + // @output_file_name mixpanel-2.8.min.js + // ==/ClosureCompiler== + + /* + SIMPLE STYLE GUIDE: + + this.x === public function + this._x === internal - only use within this file + this.__x === private - only use within the class + + Globals should be all caps + */ + + var init_type; // MODULE or SNIPPET loader + var mixpanel_master; // main mixpanel instance / object + var INIT_MODULE = 0; + var INIT_SNIPPET = 1; + + var IDENTITY_FUNC = function(x) {return x;}; + var NOOP_FUNC = function() {}; + + /** @const */ var PRIMARY_INSTANCE_NAME = 'mixpanel'; + /** @const */ var PAYLOAD_TYPE_BASE64 = 'base64'; + /** @const */ var PAYLOAD_TYPE_JSON = 'json'; + /** @const */ var DEVICE_ID_PREFIX = '$device:'; + + + /* + * Dynamic... constants? Is that an oxymoron? + */ + // http://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/ + // https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#withCredentials + var USE_XHR = (window$1.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest()); + + // IE<10 does not support cross-origin XHR's but script tags + // with defer won't block window.onload; ENQUEUE_REQUESTS + // should only be true for Opera<12 + var ENQUEUE_REQUESTS = !USE_XHR && (userAgent.indexOf('MSIE') === -1) && (userAgent.indexOf('Mozilla') === -1); + + // save reference to navigator.sendBeacon so it can be minified + var sendBeacon = null; + if (navigator['sendBeacon']) { + sendBeacon = function() { + // late reference to navigator.sendBeacon to allow patching/spying + return navigator['sendBeacon'].apply(navigator, arguments); + }; + } + + /* + * Module-level globals + */ + var DEFAULT_CONFIG = { + 'api_host': 'https://api-js.mixpanel.com', + 'api_method': 'POST', + 'api_transport': 'XHR', + 'api_payload_format': PAYLOAD_TYPE_BASE64, + 'app_host': 'https://mixpanel.com', + 'cdn': 'https://cdn.mxpnl.com', + 'cross_site_cookie': false, + 'cross_subdomain_cookie': true, + 'error_reporter': NOOP_FUNC, + 'persistence': 'cookie', + 'persistence_name': '', + 'cookie_domain': '', + 'cookie_name': '', + 'loaded': NOOP_FUNC, + 'track_marketing': true, + 'track_pageview': false, + 'skip_first_touch_marketing': false, + 'store_google': true, + 'save_referrer': true, + 'test': false, + 'verbose': false, + 'img': false, + 'debug': false, + 'track_links_timeout': 300, + 'cookie_expiration': 365, + 'upgrade': false, + 'disable_persistence': false, + 'disable_cookie': false, + 'secure_cookie': false, + 'ip': true, + 'opt_out_tracking_by_default': false, + 'opt_out_persistence_by_default': false, + 'opt_out_tracking_persistence_type': 'localStorage', + 'opt_out_tracking_cookie_prefix': null, + 'property_blacklist': [], + 'xhr_headers': {}, // { header: value, header2: value } + 'ignore_dnt': false, + 'batch_requests': true, + 'batch_size': 50, + 'batch_flush_interval_ms': 5000, + 'batch_request_timeout_ms': 90000, + 'batch_autostart': true, + 'hooks': {} + }; + + var DOM_LOADED = false; + + /** + * Mixpanel Library Object + * @constructor + */ + var MixpanelLib = function() {}; + + + /** + * create_mplib(token:string, config:object, name:string) + * + * This function is used by the init method of MixpanelLib objects + * as well as the main initializer at the end of the JSLib (that + * initializes document.mixpanel as well as any additional instances + * declared before this file has loaded). + */ + var create_mplib = function(token, config, name) { + var instance, + target = (name === PRIMARY_INSTANCE_NAME) ? mixpanel_master : mixpanel_master[name]; + + if (target && init_type === INIT_MODULE) { + instance = target; + } else { + if (target && !_.isArray(target)) { + console$1.error('You have already initialized ' + name); + return; + } + instance = new MixpanelLib(); + } + + instance._cached_groups = {}; // cache groups in a pool + + instance._init(token, config, name); + + instance['people'] = new MixpanelPeople(); + instance['people']._init(instance); + + if (!instance.get_config('skip_first_touch_marketing')) { + // We need null UTM params in the object because + // UTM parameters act as a tuple. If any UTM param + // is present, then we set all UTM params including + // empty ones together + var utm_params = _.info.campaignParams(null); + var initial_utm_params = {}; + var has_utm = false; + _.each(utm_params, function(utm_value, utm_key) { + initial_utm_params['initial_' + utm_key] = utm_value; + if (utm_value) { + has_utm = true; + } + }); + if (has_utm) { + instance['people'].set_once(initial_utm_params); + } + } + + // if any instance on the page has debug = true, we set the + // global debug to be true + Config.DEBUG = Config.DEBUG || instance.get_config('debug'); + + // if target is not defined, we called init after the lib already + // loaded, so there won't be an array of things to execute + if (!_.isUndefined(target) && _.isArray(target)) { + // Crunch through the people queue first - we queue this data up & + // flush on identify, so it's better to do all these operations first + instance._execute_array.call(instance['people'], target['people']); + instance._execute_array(target); + } + + return instance; + }; + + // Initialization methods + + /** + * This function initializes a new instance of the Mixpanel tracking object. + * All new instances are added to the main mixpanel object as sub properties (such as + * mixpanel.library_name) and also returned by this function. To define a + * second instance on the page, you would call: + * + * mixpanel.init('new token', { your: 'config' }, 'library_name'); + * + * and use it like so: + * + * mixpanel.library_name.track(...); + * + * @param {String} token Your Mixpanel API token + * @param {Object} [config] A dictionary of config options to override. See a list of default config options. + * @param {String} [name] The name for the new mixpanel instance that you want created + */ + MixpanelLib.prototype.init = function (token, config, name) { + if (_.isUndefined(name)) { + this.report_error('You must name your new library: init(token, config, name)'); + return; + } + if (name === PRIMARY_INSTANCE_NAME) { + this.report_error('You must initialize the main mixpanel object right after you include the Mixpanel js snippet'); + return; + } + + var instance = create_mplib(token, config, name); + mixpanel_master[name] = instance; + instance._loaded(); + + return instance; + }; + + // mixpanel._init(token:string, config:object, name:string) + // + // This function sets up the current instance of the mixpanel + // library. The difference between this method and the init(...) + // method is this one initializes the actual instance, whereas the + // init(...) method sets up a new library and calls _init on it. + // + MixpanelLib.prototype._init = function(token, config, name) { + config = config || {}; + + this['__loaded'] = true; + this['config'] = {}; + + var variable_features = {}; + + // default to JSON payload for standard mixpanel.com API hosts + if (!('api_payload_format' in config)) { + var api_host = config['api_host'] || DEFAULT_CONFIG['api_host']; + if (api_host.match(/\.mixpanel\.com/)) { + variable_features['api_payload_format'] = PAYLOAD_TYPE_JSON; + } + } + + this.set_config(_.extend({}, DEFAULT_CONFIG, variable_features, config, { + 'name': name, + 'token': token, + 'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc' + })); + + this['_jsc'] = NOOP_FUNC; + + this.__dom_loaded_queue = []; + this.__request_queue = []; + this.__disabled_events = []; + this._flags = { + 'disable_all_events': false, + 'identify_called': false + }; + + // set up request queueing/batching + this.request_batchers = {}; + this._batch_requests = this.get_config('batch_requests'); + if (this._batch_requests) { + if (!_.localStorage.is_supported(true) || !USE_XHR) { + this._batch_requests = false; + console$1.log('Turning off Mixpanel request-queueing; needs XHR and localStorage support'); + } else { + this.init_batchers(); + if (sendBeacon && window$1.addEventListener) { + // Before page closes or hides (user tabs away etc), attempt to flush any events + // queued up via navigator.sendBeacon. Since sendBeacon doesn't report success/failure, + // events will not be removed from the persistent store; if the site is loaded again, + // the events will be flushed again on startup and deduplicated on the Mixpanel server + // side. + // There is no reliable way to capture only page close events, so we lean on the + // visibilitychange and pagehide events as recommended at + // https://developer.mozilla.org/en-US/docs/Web/API/Window/unload_event#usage_notes. + // These events fire when the user clicks away from the current page/tab, so will occur + // more frequently than page unload, but are the only mechanism currently for capturing + // this scenario somewhat reliably. + var flush_on_unload = _.bind(function() { + if (!this.request_batchers.events.stopped) { + this.request_batchers.events.flush({unloading: true}); + } + }, this); + window$1.addEventListener('pagehide', function(ev) { + if (ev['persisted']) { + flush_on_unload(); + } + }); + window$1.addEventListener('visibilitychange', function() { + if (document$1['visibilityState'] === 'hidden') { + flush_on_unload(); + } + }); + } + } + } + + this['persistence'] = this['cookie'] = new MixpanelPersistence(this['config']); + this.unpersisted_superprops = {}; + this._gdpr_init(); + + var uuid = _.UUID(); + if (!this.get_distinct_id()) { + // There is no need to set the distinct id + // or the device id if something was already stored + // in the persitence + this.register_once({ + 'distinct_id': DEVICE_ID_PREFIX + uuid, + '$device_id': uuid + }, ''); + } + + if (this.get_config('track_pageview')) { + this.track_pageview(); + } + }; + + // Private methods + + MixpanelLib.prototype._loaded = function() { + this.get_config('loaded')(this); + this._set_default_superprops(); + }; + + // update persistence with info on referrer, UTM params, etc + MixpanelLib.prototype._set_default_superprops = function() { + this['persistence'].update_search_keyword(document$1.referrer); + if (this.get_config('store_google')) { + this.register(_.info.campaignParams(), {persistent: false}); + } + if (this.get_config('save_referrer')) { + this['persistence'].update_referrer_info(document$1.referrer); + } + }; + + MixpanelLib.prototype._dom_loaded = function() { + _.each(this.__dom_loaded_queue, function(item) { + this._track_dom.apply(this, item); + }, this); + + if (!this.has_opted_out_tracking()) { + _.each(this.__request_queue, function(item) { + this._send_request.apply(this, item); + }, this); + } + + delete this.__dom_loaded_queue; + delete this.__request_queue; + }; + + MixpanelLib.prototype._track_dom = function(DomClass, args) { + if (this.get_config('img')) { + this.report_error('You can\'t use DOM tracking functions with img = true.'); + return false; + } + + if (!DOM_LOADED) { + this.__dom_loaded_queue.push([DomClass, args]); + return false; + } + + var dt = new DomClass().init(this); + return dt.track.apply(dt, args); + }; + + /** + * _prepare_callback() should be called by callers of _send_request for use + * as the callback argument. + * + * If there is no callback, this returns null. + * If we are going to make XHR/XDR requests, this returns a function. + * If we are going to use script tags, this returns a string to use as the + * callback GET param. + */ + MixpanelLib.prototype._prepare_callback = function(callback, data) { + if (_.isUndefined(callback)) { + return null; + } + + if (USE_XHR) { + var callback_function = function(response) { + callback(response, data); + }; + return callback_function; + } else { + // if the user gives us a callback, we store as a random + // property on this instances jsc function and update our + // callback string to reflect that. + var jsc = this['_jsc']; + var randomized_cb = '' + Math.floor(Math.random() * 100000000); + var callback_string = this.get_config('callback_fn') + '[' + randomized_cb + ']'; + jsc[randomized_cb] = function(response) { + delete jsc[randomized_cb]; + callback(response, data); + }; + return callback_string; + } + }; + + MixpanelLib.prototype._send_request = function(url, data, options, callback) { + var succeeded = true; + + if (ENQUEUE_REQUESTS) { + this.__request_queue.push(arguments); + return succeeded; + } + + var DEFAULT_OPTIONS = { + method: this.get_config('api_method'), + transport: this.get_config('api_transport'), + verbose: this.get_config('verbose') + }; + var body_data = null; + + if (!callback && (_.isFunction(options) || typeof options === 'string')) { + callback = options; + options = null; + } + options = _.extend(DEFAULT_OPTIONS, options || {}); + if (!USE_XHR) { + options.method = 'GET'; + } + var use_post = options.method === 'POST'; + var use_sendBeacon = sendBeacon && use_post && options.transport.toLowerCase() === 'sendbeacon'; + + // needed to correctly format responses + var verbose_mode = options.verbose; + if (data['verbose']) { verbose_mode = true; } + + if (this.get_config('test')) { data['test'] = 1; } + if (verbose_mode) { data['verbose'] = 1; } + if (this.get_config('img')) { data['img'] = 1; } + if (!USE_XHR) { + if (callback) { + data['callback'] = callback; + } else if (verbose_mode || this.get_config('test')) { + // Verbose output (from verbose mode, or an error in test mode) is a json blob, + // which by itself is not valid javascript. Without a callback, this verbose output will + // cause an error when returned via jsonp, so we force a no-op callback param. + // See the ECMA script spec: http://www.ecma-international.org/ecma-262/5.1/#sec-12.4 + data['callback'] = '(function(){})'; + } + } + + data['ip'] = this.get_config('ip')?1:0; + data['_'] = new Date().getTime().toString(); + + if (use_post) { + body_data = 'data=' + encodeURIComponent(data['data']); + delete data['data']; + } + + url += '?' + _.HTTPBuildQuery(data); + + var lib = this; + if ('img' in data) { + var img = document$1.createElement('img'); + img.src = url; + document$1.body.appendChild(img); + } else if (use_sendBeacon) { + try { + succeeded = sendBeacon(url, body_data); + } catch (e) { + lib.report_error(e); + succeeded = false; + } + try { + if (callback) { + callback(succeeded ? 1 : 0); + } + } catch (e) { + lib.report_error(e); + } + } else if (USE_XHR) { + try { + var req = new XMLHttpRequest(); + req.open(options.method, url, true); + + var headers = this.get_config('xhr_headers'); + if (use_post) { + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } + _.each(headers, function(headerValue, headerName) { + req.setRequestHeader(headerName, headerValue); + }); + + if (options.timeout_ms && typeof req.timeout !== 'undefined') { + req.timeout = options.timeout_ms; + var start_time = new Date().getTime(); + } + + // send the mp_optout cookie + // withCredentials cannot be modified until after calling .open on Android and Mobile Safari + req.withCredentials = true; + req.onreadystatechange = function () { + if (req.readyState === 4) { // XMLHttpRequest.DONE == 4, except in safari 4 + if (req.status === 200) { + if (callback) { + if (verbose_mode) { + var response; + try { + response = _.JSONDecode(req.responseText); + } catch (e) { + lib.report_error(e); + if (options.ignore_json_errors) { + response = req.responseText; + } else { + return; + } + } + callback(response); + } else { + callback(Number(req.responseText)); + } + } + } else { + var error; + if ( + req.timeout && + !req.status && + new Date().getTime() - start_time >= req.timeout + ) { + error = 'timeout'; + } else { + error = 'Bad HTTP status: ' + req.status + ' ' + req.statusText; + } + lib.report_error(error); + if (callback) { + if (verbose_mode) { + callback({status: 0, error: error, xhr_req: req}); + } else { + callback(0); + } + } + } + } + }; + req.send(body_data); + } catch (e) { + lib.report_error(e); + succeeded = false; + } + } else { + var script = document$1.createElement('script'); + script.type = 'text/javascript'; + script.async = true; + script.defer = true; + script.src = url; + var s = document$1.getElementsByTagName('script')[0]; + s.parentNode.insertBefore(script, s); + } + + return succeeded; + }; + + /** + * _execute_array() deals with processing any mixpanel function + * calls that were called before the Mixpanel library were loaded + * (and are thus stored in an array so they can be called later) + * + * Note: we fire off all the mixpanel function calls && user defined + * functions BEFORE we fire off mixpanel tracking calls. This is so + * identify/register/set_config calls can properly modify early + * tracking calls. + * + * @param {Array} array + */ + MixpanelLib.prototype._execute_array = function(array) { + var fn_name, alias_calls = [], other_calls = [], tracking_calls = []; + _.each(array, function(item) { + if (item) { + fn_name = item[0]; + if (_.isArray(fn_name)) { + tracking_calls.push(item); // chained call e.g. mixpanel.get_group().set() + } else if (typeof(item) === 'function') { + item.call(this); + } else if (_.isArray(item) && fn_name === 'alias') { + alias_calls.push(item); + } else if (_.isArray(item) && fn_name.indexOf('track') !== -1 && typeof(this[fn_name]) === 'function') { + tracking_calls.push(item); + } else { + other_calls.push(item); + } + } + }, this); + + var execute = function(calls, context) { + _.each(calls, function(item) { + if (_.isArray(item[0])) { + // chained call + var caller = context; + _.each(item, function(call) { + caller = caller[call[0]].apply(caller, call.slice(1)); + }); + } else { + this[item[0]].apply(this, item.slice(1)); + } + }, context); + }; + + execute(alias_calls, this); + execute(other_calls, this); + execute(tracking_calls, this); + }; + + // request queueing utils + + MixpanelLib.prototype.are_batchers_initialized = function() { + return !!this.request_batchers.events; + }; + + MixpanelLib.prototype.init_batchers = function() { + var token = this.get_config('token'); + if (!this.are_batchers_initialized()) { + var batcher_for = _.bind(function(attrs) { + return new RequestBatcher( + '__mpq_' + token + attrs.queue_suffix, + { + libConfig: this['config'], + sendRequestFunc: _.bind(function(data, options, cb) { + this._send_request( + this.get_config('api_host') + attrs.endpoint, + this._encode_data_for_request(data), + options, + this._prepare_callback(cb, data) + ); + }, this), + beforeSendHook: _.bind(function(item) { + return this._run_hook('before_send_' + attrs.type, item); + }, this), + errorReporter: this.get_config('error_reporter'), + stopAllBatchingFunc: _.bind(this.stop_batch_senders, this) + } + ); + }, this); + this.request_batchers = { + events: batcher_for({type: 'events', endpoint: '/track/', queue_suffix: '_ev'}), + people: batcher_for({type: 'people', endpoint: '/engage/', queue_suffix: '_pp'}), + groups: batcher_for({type: 'groups', endpoint: '/groups/', queue_suffix: '_gr'}) + }; + } + if (this.get_config('batch_autostart')) { + this.start_batch_senders(); + } + }; + + MixpanelLib.prototype.start_batch_senders = function() { + if (this.are_batchers_initialized()) { + this._batch_requests = true; + _.each(this.request_batchers, function(batcher) { + batcher.start(); + }); + } + }; + + MixpanelLib.prototype.stop_batch_senders = function() { + this._batch_requests = false; + _.each(this.request_batchers, function(batcher) { + batcher.stop(); + batcher.clear(); + }); + }; + + /** + * push() keeps the standard async-array-push + * behavior around after the lib is loaded. + * This is only useful for external integrations that + * do not wish to rely on our convenience methods + * (created in the snippet). + * + * ### Usage: + * mixpanel.push(['register', { a: 'b' }]); + * + * @param {Array} item A [function_name, args...] array to be executed + */ + MixpanelLib.prototype.push = function(item) { + this._execute_array([item]); + }; + + /** + * Disable events on the Mixpanel object. If passed no arguments, + * this function disables tracking of any event. If passed an + * array of event names, those events will be disabled, but other + * events will continue to be tracked. + * + * Note: this function does not stop other mixpanel functions from + * firing, such as register() or people.set(). + * + * @param {Array} [events] An array of event names to disable + */ + MixpanelLib.prototype.disable = function(events) { + if (typeof(events) === 'undefined') { + this._flags.disable_all_events = true; + } else { + this.__disabled_events = this.__disabled_events.concat(events); + } + }; + + MixpanelLib.prototype._encode_data_for_request = function(data) { + var encoded_data = _.JSONEncode(data); + if (this.get_config('api_payload_format') === PAYLOAD_TYPE_BASE64) { + encoded_data = _.base64Encode(encoded_data); + } + return {'data': encoded_data}; + }; + + // internal method for handling track vs batch-enqueue logic + MixpanelLib.prototype._track_or_batch = function(options, callback) { + var truncated_data = _.truncate(options.data, 255); + var endpoint = options.endpoint; + var batcher = options.batcher; + var should_send_immediately = options.should_send_immediately; + var send_request_options = options.send_request_options || {}; + callback = callback || NOOP_FUNC; + + var request_enqueued_or_initiated = true; + var send_request_immediately = _.bind(function() { + if (!send_request_options.skip_hooks) { + truncated_data = this._run_hook('before_send_' + options.type, truncated_data); + } + if (truncated_data) { + console$1.log('MIXPANEL REQUEST:'); + console$1.log(truncated_data); + return this._send_request( + endpoint, + this._encode_data_for_request(truncated_data), + send_request_options, + this._prepare_callback(callback, truncated_data) + ); + } else { + return null; + } + }, this); + + if (this._batch_requests && !should_send_immediately) { + batcher.enqueue(truncated_data, function(succeeded) { + if (succeeded) { + callback(1, truncated_data); + } else { + send_request_immediately(); + } + }); + } else { + request_enqueued_or_initiated = send_request_immediately(); + } + + return request_enqueued_or_initiated && truncated_data; + }; + + /** + * Track an event. This is the most important and + * frequently used Mixpanel function. + * + * ### Usage: + * + * // track an event named 'Registered' + * mixpanel.track('Registered', {'Gender': 'Male', 'Age': 21}); + * + * // track an event using navigator.sendBeacon + * mixpanel.track('Left page', {'duration_seconds': 35}, {transport: 'sendBeacon'}); + * + * To track link clicks or form submissions, see track_links() or track_forms(). + * + * @param {String} event_name The name of the event. This can be anything the user does - 'Button Click', 'Sign Up', 'Item Purchased', etc. + * @param {Object} [properties] A set of properties to include with the event you're sending. These describe the user who did the event or details about the event itself. + * @param {Object} [options] Optional configuration for this track request. + * @param {String} [options.transport] Transport method for network request ('xhr' or 'sendBeacon'). + * @param {Boolean} [options.send_immediately] Whether to bypass batching/queueing and send track request immediately. + * @param {Function} [callback] If provided, the callback function will be called after tracking the event. + * @returns {Boolean|Object} If the tracking request was successfully initiated/queued, an object + * with the tracking payload sent to the API server is returned; otherwise false. + */ + MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, properties, options, callback) { + if (!callback && typeof options === 'function') { + callback = options; + options = null; + } + options = options || {}; + var transport = options['transport']; // external API, don't minify 'transport' prop + if (transport) { + options.transport = transport; // 'transport' prop name can be minified internally + } + var should_send_immediately = options['send_immediately']; + if (typeof callback !== 'function') { + callback = NOOP_FUNC; + } + + if (_.isUndefined(event_name)) { + this.report_error('No event name provided to mixpanel.track'); + return; + } + + if (this._event_is_disabled(event_name)) { + callback(0); + return; + } + + // set defaults + properties = properties || {}; + properties['token'] = this.get_config('token'); + + // set $duration if time_event was previously called for this event + var start_timestamp = this['persistence'].remove_event_timer(event_name); + if (!_.isUndefined(start_timestamp)) { + var duration_in_ms = new Date().getTime() - start_timestamp; + properties['$duration'] = parseFloat((duration_in_ms / 1000).toFixed(3)); + } + + this._set_default_superprops(); + + var marketing_properties = this.get_config('track_marketing') + ? _.info.marketingParams() + : {}; + + // note: extend writes to the first object, so lets make sure we + // don't write to the persistence properties object and info + // properties object by passing in a new object + + // update properties with pageview info and super-properties + properties = _.extend( + {}, + _.info.properties(), + marketing_properties, + this['persistence'].properties(), + this.unpersisted_superprops, + properties + ); + + var property_blacklist = this.get_config('property_blacklist'); + if (_.isArray(property_blacklist)) { + _.each(property_blacklist, function(blacklisted_prop) { + delete properties[blacklisted_prop]; + }); + } else { + this.report_error('Invalid value for property_blacklist config: ' + property_blacklist); + } + + var data = { + 'event': event_name, + 'properties': properties + }; + var ret = this._track_or_batch({ + type: 'events', + data: data, + endpoint: this.get_config('api_host') + '/track/', + batcher: this.request_batchers.events, + should_send_immediately: should_send_immediately, + send_request_options: options + }, callback); + + return ret; + }); + + /** + * Register the current user into one/many groups. + * + * ### Usage: + * + * mixpanel.set_group('company', ['mixpanel', 'google']) // an array of IDs + * mixpanel.set_group('company', 'mixpanel') + * mixpanel.set_group('company', 128746312) + * + * @param {String} group_key Group key + * @param {Array|String|Number} group_ids An array of group IDs, or a singular group ID + * @param {Function} [callback] If provided, the callback will be called after tracking the event. + * + */ + MixpanelLib.prototype.set_group = addOptOutCheckMixpanelLib(function(group_key, group_ids, callback) { + if (!_.isArray(group_ids)) { + group_ids = [group_ids]; + } + var prop = {}; + prop[group_key] = group_ids; + this.register(prop); + return this['people'].set(group_key, group_ids, callback); + }); + + /** + * Add a new group for this user. + * + * ### Usage: + * + * mixpanel.add_group('company', 'mixpanel') + * + * @param {String} group_key Group key + * @param {*} group_id A valid Mixpanel property type + * @param {Function} [callback] If provided, the callback will be called after tracking the event. + */ + MixpanelLib.prototype.add_group = addOptOutCheckMixpanelLib(function(group_key, group_id, callback) { + var old_values = this.get_property(group_key); + if (old_values === undefined) { + var prop = {}; + prop[group_key] = [group_id]; + this.register(prop); + } else { + if (old_values.indexOf(group_id) === -1) { + old_values.push(group_id); + this.register(prop); + } + } + return this['people'].union(group_key, group_id, callback); + }); + + /** + * Remove a group from this user. + * + * ### Usage: + * + * mixpanel.remove_group('company', 'mixpanel') + * + * @param {String} group_key Group key + * @param {*} group_id A valid Mixpanel property type + * @param {Function} [callback] If provided, the callback will be called after tracking the event. + */ + MixpanelLib.prototype.remove_group = addOptOutCheckMixpanelLib(function(group_key, group_id, callback) { + var old_value = this.get_property(group_key); + // if the value doesn't exist, the persistent store is unchanged + if (old_value !== undefined) { + var idx = old_value.indexOf(group_id); + if (idx > -1) { + old_value.splice(idx, 1); + this.register({group_key: old_value}); + } + if (old_value.length === 0) { + this.unregister(group_key); + } + } + return this['people'].remove(group_key, group_id, callback); + }); + + /** + * Track an event with specific groups. + * + * ### Usage: + * + * mixpanel.track_with_groups('purchase', {'product': 'iphone'}, {'University': ['UCB', 'UCLA']}) + * + * @param {String} event_name The name of the event (see `mixpanel.track()`) + * @param {Object=} properties A set of properties to include with the event you're sending (see `mixpanel.track()`) + * @param {Object=} groups An object mapping group name keys to one or more values + * @param {Function} [callback] If provided, the callback will be called after tracking the event. + */ + MixpanelLib.prototype.track_with_groups = addOptOutCheckMixpanelLib(function(event_name, properties, groups, callback) { + var tracking_props = _.extend({}, properties || {}); + _.each(groups, function(v, k) { + if (v !== null && v !== undefined) { + tracking_props[k] = v; + } + }); + return this.track(event_name, tracking_props, callback); + }); + + MixpanelLib.prototype._create_map_key = function (group_key, group_id) { + return group_key + '_' + JSON.stringify(group_id); + }; + + MixpanelLib.prototype._remove_group_from_cache = function (group_key, group_id) { + delete this._cached_groups[this._create_map_key(group_key, group_id)]; + }; + + /** + * Look up reference to a Mixpanel group + * + * ### Usage: + * + * mixpanel.get_group(group_key, group_id) + * + * @param {String} group_key Group key + * @param {Object} group_id A valid Mixpanel property type + * @returns {Object} A MixpanelGroup identifier + */ + MixpanelLib.prototype.get_group = function (group_key, group_id) { + var map_key = this._create_map_key(group_key, group_id); + var group = this._cached_groups[map_key]; + if (group === undefined || group._group_key !== group_key || group._group_id !== group_id) { + group = new MixpanelGroup(); + group._init(this, group_key, group_id); + this._cached_groups[map_key] = group; + } + return group; + }; + + /** + * Track a default Mixpanel page view event, which includes extra default event properties to + * improve page view data. The `config.track_pageview` option for mixpanel.init() + * may be turned on for tracking page loads automatically. + * + * ### Usage + * + * // track a default $mp_web_page_view event + * mixpanel.track_pageview(); + * + * // track a page view event with additional event properties + * mixpanel.track_pageview({'ab_test_variant': 'card-layout-b'}); + * + * // example approach to track page views on different page types as event properties + * mixpanel.track_pageview({'page': 'pricing'}); + * mixpanel.track_pageview({'page': 'homepage'}); + * + * // UNCOMMON: Tracking a page view event with a custom event_name option. NOT expected to be used for + * // individual pages on the same site or product. Use cases for custom event_name may be page + * // views on different products or internal applications that are considered completely separate + * mixpanel.track_pageview({'page': 'customer-search'}, {'event_name': '[internal] Admin Page View'}); + * + * @param {Object} [properties] An optional set of additional properties to send with the page view event + * @param {Object} [options] Page view tracking options + * @param {String} [options.event_name] - Alternate name for the tracking event + * @returns {Boolean|Object} If the tracking request was successfully initiated/queued, an object + * with the tracking payload sent to the API server is returned; otherwise false. + */ + MixpanelLib.prototype.track_pageview = addOptOutCheckMixpanelLib(function(properties, options) { + if (typeof properties !== 'object') { + properties = {}; + } + options = options || {}; + var event_name = options['event_name'] || '$mp_web_page_view'; + + var default_page_properties = _.extend( + _.info.mpPageViewProperties(), + _.info.campaignParams(), + _.info.clickParams() + ); + + var event_properties = _.extend( + {}, + default_page_properties, + properties + ); + + return this.track(event_name, event_properties); + }); + + /** + * Track clicks on a set of document elements. Selector must be a + * valid query. Elements must exist on the page at the time track_links is called. + * + * ### Usage: + * + * // track click for link id #nav + * mixpanel.track_links('#nav', 'Clicked Nav Link'); + * + * ### Notes: + * + * This function will wait up to 300 ms for the Mixpanel + * servers to respond. If they have not responded by that time + * it will head to the link without ensuring that your event + * has been tracked. To configure this timeout please see the + * set_config() documentation below. + * + * If you pass a function in as the properties argument, the + * function will receive the DOMElement that triggered the + * event as an argument. You are expected to return an object + * from the function; any properties defined on this object + * will be sent to mixpanel as event properties. + * + * @type {Function} + * @param {Object|String} query A valid DOM query, element or jQuery-esque list + * @param {String} event_name The name of the event to track + * @param {Object|Function} [properties] A properties object or function that returns a dictionary of properties when passed a DOMElement + */ + MixpanelLib.prototype.track_links = function() { + return this._track_dom.call(this, LinkTracker, arguments); + }; + + /** + * Track form submissions. Selector must be a valid query. + * + * ### Usage: + * + * // track submission for form id 'register' + * mixpanel.track_forms('#register', 'Created Account'); + * + * ### Notes: + * + * This function will wait up to 300 ms for the mixpanel + * servers to respond, if they have not responded by that time + * it will head to the link without ensuring that your event + * has been tracked. To configure this timeout please see the + * set_config() documentation below. + * + * If you pass a function in as the properties argument, the + * function will receive the DOMElement that triggered the + * event as an argument. You are expected to return an object + * from the function; any properties defined on this object + * will be sent to mixpanel as event properties. + * + * @type {Function} + * @param {Object|String} query A valid DOM query, element or jQuery-esque list + * @param {String} event_name The name of the event to track + * @param {Object|Function} [properties] This can be a set of properties, or a function that returns a set of properties after being passed a DOMElement + */ + MixpanelLib.prototype.track_forms = function() { + return this._track_dom.call(this, FormTracker, arguments); + }; + + /** + * Time an event by including the time between this call and a + * later 'track' call for the same event in the properties sent + * with the event. + * + * ### Usage: + * + * // time an event named 'Registered' + * mixpanel.time_event('Registered'); + * mixpanel.track('Registered', {'Gender': 'Male', 'Age': 21}); + * + * When called for a particular event name, the next track call for that event + * name will include the elapsed time between the 'time_event' and 'track' + * calls. This value is stored as seconds in the '$duration' property. + * + * @param {String} event_name The name of the event. + */ + MixpanelLib.prototype.time_event = function(event_name) { + if (_.isUndefined(event_name)) { + this.report_error('No event name provided to mixpanel.time_event'); + return; + } + + if (this._event_is_disabled(event_name)) { + return; + } + + this['persistence'].set_event_timer(event_name, new Date().getTime()); + }; + + var REGISTER_DEFAULTS = { + 'persistent': true + }; + /** + * Helper to parse options param for register methods, maintaining + * legacy support for plain "days" param instead of options object + * @param {Number|Object} [days_or_options] 'days' option (Number), or Options object for register methods + * @returns {Object} options object + */ + var options_for_register = function(days_or_options) { + var options; + if (_.isObject(days_or_options)) { + options = days_or_options; + } else if (!_.isUndefined(days_or_options)) { + options = {'days': days_or_options}; + } else { + options = {}; + } + return _.extend({}, REGISTER_DEFAULTS, options); + }; + + /** + * Register a set of super properties, which are included with all + * events. This will overwrite previous super property values. + * + * ### Usage: + * + * // register 'Gender' as a super property + * mixpanel.register({'Gender': 'Female'}); + * + * // register several super properties when a user signs up + * mixpanel.register({ + * 'Email': 'jdoe@example.com', + * 'Account Type': 'Free' + * }); + * + * // register only for the current pageload + * mixpanel.register({'Name': 'Pat'}, {persistent: false}); + * + * @param {Object} properties An associative array of properties to store about the user + * @param {Number|Object} [days_or_options] Options object or number of days since the user's last visit to store the super properties (only valid for persisted props) + * @param {boolean} [days_or_options.days] - number of days since the user's last visit to store the super properties (only valid for persisted props) + * @param {boolean} [days_or_options.persistent=true] - whether to put in persistent storage (cookie/localStorage) + */ + MixpanelLib.prototype.register = function(props, days_or_options) { + var options = options_for_register(days_or_options); + if (options['persistent']) { + this['persistence'].register(props, options['days']); + } else { + _.extend(this.unpersisted_superprops, props); + } + }; + + /** + * Register a set of super properties only once. This will not + * overwrite previous super property values, unlike register(). + * + * ### Usage: + * + * // register a super property for the first time only + * mixpanel.register_once({ + * 'First Login Date': new Date().toISOString() + * }); + * + * // register once, only for the current pageload + * mixpanel.register_once({ + * 'First interaction time': new Date().toISOString() + * }, 'None', {persistent: false}); + * + * ### Notes: + * + * If default_value is specified, current super properties + * with that value will be overwritten. + * + * @param {Object} properties An associative array of properties to store about the user + * @param {*} [default_value] Value to override if already set in super properties (ex: 'False') Default: 'None' + * @param {Number|Object} [days_or_options] Options object or number of days since the user's last visit to store the super properties (only valid for persisted props) + * @param {boolean} [days_or_options.days] - number of days since the user's last visit to store the super properties (only valid for persisted props) + * @param {boolean} [days_or_options.persistent=true] - whether to put in persistent storage (cookie/localStorage) + */ + MixpanelLib.prototype.register_once = function(props, default_value, days_or_options) { + var options = options_for_register(days_or_options); + if (options['persistent']) { + this['persistence'].register_once(props, default_value, options['days']); + } else { + if (typeof(default_value) === 'undefined') { + default_value = 'None'; + } + _.each(props, function(val, prop) { + if (!this.unpersisted_superprops.hasOwnProperty(prop) || this.unpersisted_superprops[prop] === default_value) { + this.unpersisted_superprops[prop] = val; + } + }, this); + } + }; + + /** + * Delete a super property stored with the current user. + * + * @param {String} property The name of the super property to remove + * @param {Object} [options] + * @param {boolean} [options.persistent=true] - whether to look in persistent storage (cookie/localStorage) + */ + MixpanelLib.prototype.unregister = function(property, options) { + options = options_for_register(options); + if (options['persistent']) { + this['persistence'].unregister(property); + } else { + delete this.unpersisted_superprops[property]; + } + }; + + MixpanelLib.prototype._register_single = function(prop, value) { + var props = {}; + props[prop] = value; + this.register(props); + }; + + /** + * Identify a user with a unique ID to track user activity across + * devices, tie a user to their events, and create a user profile. + * If you never call this method, unique visitors are tracked using + * a UUID generated the first time they visit the site. + * + * Call identify when you know the identity of the current user, + * typically after login or signup. We recommend against using + * identify for anonymous visitors to your site. + * + * ### Notes: + * If your project has + * ID Merge + * enabled, the identify method will connect pre- and + * post-authentication events when appropriate. + * + * If your project does not have ID Merge enabled, identify will + * change the user's local distinct_id to the unique ID you pass. + * Events tracked prior to authentication will not be connected + * to the same user identity. If ID Merge is disabled, alias can + * be used to connect pre- and post-registration events. + * + * @param {String} [unique_id] A string that uniquely identifies a user. If not provided, the distinct_id currently in the persistent store (cookie or localStorage) will be used. + */ + MixpanelLib.prototype.identify = function( + new_distinct_id, _set_callback, _add_callback, _append_callback, _set_once_callback, _union_callback, _unset_callback, _remove_callback + ) { + // Optional Parameters + // _set_callback:function A callback to be run if and when the People set queue is flushed + // _add_callback:function A callback to be run if and when the People add queue is flushed + // _append_callback:function A callback to be run if and when the People append queue is flushed + // _set_once_callback:function A callback to be run if and when the People set_once queue is flushed + // _union_callback:function A callback to be run if and when the People union queue is flushed + // _unset_callback:function A callback to be run if and when the People unset queue is flushed + + var previous_distinct_id = this.get_distinct_id(); + if (new_distinct_id && previous_distinct_id !== new_distinct_id) { + // we allow the following condition if previous distinct_id is same as new_distinct_id + // so that you can force flush people updates for anonymous profiles. + if (typeof new_distinct_id === 'string' && new_distinct_id.indexOf(DEVICE_ID_PREFIX) === 0) { + this.report_error('distinct_id cannot have $device: prefix'); + return -1; + } + this.register({'$user_id': new_distinct_id}); + } + + if (!this.get_property('$device_id')) { + // The persisted distinct id might not actually be a device id at all + // it might be a distinct id of the user from before + var device_id = previous_distinct_id; + this.register_once({ + '$had_persisted_distinct_id': true, + '$device_id': device_id + }, ''); + } + + // identify only changes the distinct id if it doesn't match either the existing or the alias; + // if it's new, blow away the alias as well. + if (new_distinct_id !== previous_distinct_id && new_distinct_id !== this.get_property(ALIAS_ID_KEY)) { + this.unregister(ALIAS_ID_KEY); + this.register({'distinct_id': new_distinct_id}); + } + this._flags.identify_called = true; + // Flush any queued up people requests + this['people']._flush(_set_callback, _add_callback, _append_callback, _set_once_callback, _union_callback, _unset_callback, _remove_callback); + + // send an $identify event any time the distinct_id is changing - logic on the server + // will determine whether or not to do anything with it. + if (new_distinct_id !== previous_distinct_id) { + this.track('$identify', { + 'distinct_id': new_distinct_id, + '$anon_distinct_id': previous_distinct_id + }, {skip_hooks: true}); + } + }; + + /** + * Clears super properties and generates a new random distinct_id for this instance. + * Useful for clearing data when a user logs out. + */ + MixpanelLib.prototype.reset = function() { + this['persistence'].clear(); + this._flags.identify_called = false; + var uuid = _.UUID(); + this.register_once({ + 'distinct_id': DEVICE_ID_PREFIX + uuid, + '$device_id': uuid + }, ''); + }; + + /** + * Returns the current distinct id of the user. This is either the id automatically + * generated by the library or the id that has been passed by a call to identify(). + * + * ### Notes: + * + * get_distinct_id() can only be called after the Mixpanel library has finished loading. + * init() has a loaded function available to handle this automatically. For example: + * + * // set distinct_id after the mixpanel library has loaded + * mixpanel.init('YOUR PROJECT TOKEN', { + * loaded: function(mixpanel) { + * distinct_id = mixpanel.get_distinct_id(); + * } + * }); + */ + MixpanelLib.prototype.get_distinct_id = function() { + return this.get_property('distinct_id'); + }; + + /** + * The alias method creates an alias which Mixpanel will use to + * remap one id to another. Multiple aliases can point to the + * same identifier. + * + * The following is a valid use of alias: + * + * mixpanel.alias('new_id', 'existing_id'); + * // You can add multiple id aliases to the existing ID + * mixpanel.alias('newer_id', 'existing_id'); + * + * Aliases can also be chained - the following is a valid example: + * + * mixpanel.alias('new_id', 'existing_id'); + * // chain newer_id - new_id - existing_id + * mixpanel.alias('newer_id', 'new_id'); + * + * Aliases cannot point to multiple identifiers - the following + * example will not work: + * + * mixpanel.alias('new_id', 'existing_id'); + * // this is invalid as 'new_id' already points to 'existing_id' + * mixpanel.alias('new_id', 'newer_id'); + * + * ### Notes: + * + * If your project does not have + * ID Merge + * enabled, the best practice is to call alias once when a unique + * ID is first created for a user (e.g., when a user first registers + * for an account). Do not use alias multiple times for a single + * user without ID Merge enabled. + * + * @param {String} alias A unique identifier that you want to use for this user in the future. + * @param {String} [original] The current identifier being used for this user. + */ + MixpanelLib.prototype.alias = function(alias, original) { + // If the $people_distinct_id key exists in persistence, there has been a previous + // mixpanel.people.identify() call made for this user. It is VERY BAD to make an alias with + // this ID, as it will duplicate users. + if (alias === this.get_property(PEOPLE_DISTINCT_ID_KEY)) { + this.report_error('Attempting to create alias for existing People user - aborting.'); + return -2; + } + + var _this = this; + if (_.isUndefined(original)) { + original = this.get_distinct_id(); + } + if (alias !== original) { + this._register_single(ALIAS_ID_KEY, alias); + return this.track('$create_alias', { + 'alias': alias, + 'distinct_id': original + }, { + skip_hooks: true + }, function() { + // Flush the people queue + _this.identify(alias); + }); + } else { + this.report_error('alias matches current distinct_id - skipping api call.'); + this.identify(alias); + return -1; + } + }; + + /** + * Provide a string to recognize the user by. The string passed to + * this method will appear in the Mixpanel Streams product rather + * than an automatically generated name. Name tags do not have to + * be unique. + * + * This value will only be included in Streams data. + * + * @param {String} name_tag A human readable name for the user + * @deprecated + */ + MixpanelLib.prototype.name_tag = function(name_tag) { + this._register_single('mp_name_tag', name_tag); + }; + + /** + * Update the configuration of a mixpanel library instance. + * + * The default config is: + * + * { + * // HTTP method for tracking requests + * api_method: 'POST' + * + * // transport for sending requests ('XHR' or 'sendBeacon') + * // NB: sendBeacon should only be used for scenarios such as + * // page unload where a "best-effort" attempt to send is + * // acceptable; the sendBeacon API does not support callbacks + * // or any way to know the result of the request. Mixpanel + * // tracking via sendBeacon will not support any event- + * // batching or retry mechanisms. + * api_transport: 'XHR' + * + * // request-batching/queueing/retry + * batch_requests: true, + * + * // maximum number of events/updates to send in a single + * // network request + * batch_size: 50, + * + * // milliseconds to wait between sending batch requests + * batch_flush_interval_ms: 5000, + * + * // milliseconds to wait for network responses to batch requests + * // before they are considered timed-out and retried + * batch_request_timeout_ms: 90000, + * + * // override value for cookie domain, only useful for ensuring + * // correct cross-subdomain cookies on unusual domains like + * // subdomain.mainsite.avocat.fr; NB this cannot be used to + * // set cookies on a different domain than the current origin + * cookie_domain: '' + * + * // super properties cookie expiration (in days) + * cookie_expiration: 365 + * + * // if true, cookie will be set with SameSite=None; Secure + * // this is only useful in special situations, like embedded + * // 3rd-party iframes that set up a Mixpanel instance + * cross_site_cookie: false + * + * // super properties span subdomains + * cross_subdomain_cookie: true + * + * // debug mode + * debug: false + * + * // if this is true, the mixpanel cookie or localStorage entry + * // will be deleted, and no user persistence will take place + * disable_persistence: false + * + * // if this is true, Mixpanel will automatically determine + * // City, Region and Country data using the IP address of + * //the client + * ip: true + * + * // opt users out of tracking by this Mixpanel instance by default + * opt_out_tracking_by_default: false + * + * // opt users out of browser data storage by this Mixpanel instance by default + * opt_out_persistence_by_default: false + * + * // persistence mechanism used by opt-in/opt-out methods - cookie + * // or localStorage - falls back to cookie if localStorage is unavailable + * opt_out_tracking_persistence_type: 'localStorage' + * + * // customize the name of cookie/localStorage set by opt-in/opt-out methods + * opt_out_tracking_cookie_prefix: null + * + * // type of persistent store for super properties (cookie/ + * // localStorage) if set to 'localStorage', any existing + * // mixpanel cookie value with the same persistence_name + * // will be transferred to localStorage and deleted + * persistence: 'cookie' + * + * // name for super properties persistent store + * persistence_name: '' + * + * // names of properties/superproperties which should never + * // be sent with track() calls + * property_blacklist: [] + * + * // if this is true, mixpanel cookies will be marked as + * // secure, meaning they will only be transmitted over https + * secure_cookie: false + * + * // disables enriching user profiles with first touch marketing data + * skip_first_touch_marketing: false + * + * // the amount of time track_links will + * // wait for Mixpanel's servers to respond + * track_links_timeout: 300 + * + * // adds any UTM parameters and click IDs present on the page to any events fired + * track_marketing: true + * + * // enables automatic page view tracking using default page view events through + * // the track_pageview() method + * track_pageview: false + * + * // if you set upgrade to be true, the library will check for + * // a cookie from our old js library and import super + * // properties from it, then the old cookie is deleted + * // The upgrade config option only works in the initialization, + * // so make sure you set it when you create the library. + * upgrade: false + * + * // extra HTTP request headers to set for each API request, in + * // the format {'Header-Name': value} + * xhr_headers: {} + * + * // whether to ignore or respect the web browser's Do Not Track setting + * ignore_dnt: false + * } + * + * + * @param {Object} config A dictionary of new configuration values to update + */ + MixpanelLib.prototype.set_config = function(config) { + if (_.isObject(config)) { + _.extend(this['config'], config); + + var new_batch_size = config['batch_size']; + if (new_batch_size) { + _.each(this.request_batchers, function(batcher) { + batcher.resetBatchSize(); + }); + } + + if (!this.get_config('persistence_name')) { + this['config']['persistence_name'] = this['config']['cookie_name']; + } + if (!this.get_config('disable_persistence')) { + this['config']['disable_persistence'] = this['config']['disable_cookie']; + } + + if (this['persistence']) { + this['persistence'].update_config(this['config']); + } + Config.DEBUG = Config.DEBUG || this.get_config('debug'); + } + }; + + /** + * returns the current config object for the library. + */ + MixpanelLib.prototype.get_config = function(prop_name) { + return this['config'][prop_name]; + }; + + /** + * Fetch a hook function from config, with safe default, and run it + * against the given arguments + * @param {string} hook_name which hook to retrieve + * @returns {any|null} return value of user-provided hook, or null if nothing was returned + */ + MixpanelLib.prototype._run_hook = function(hook_name) { + var ret = (this['config']['hooks'][hook_name] || IDENTITY_FUNC).apply(this, slice.call(arguments, 1)); + if (typeof ret === 'undefined') { + this.report_error(hook_name + ' hook did not return a value'); + ret = null; + } + return ret; + }; + + /** + * Returns the value of the super property named property_name. If no such + * property is set, get_property() will return the undefined value. + * + * ### Notes: + * + * get_property() can only be called after the Mixpanel library has finished loading. + * init() has a loaded function available to handle this automatically. For example: + * + * // grab value for 'user_id' after the mixpanel library has loaded + * mixpanel.init('YOUR PROJECT TOKEN', { + * loaded: function(mixpanel) { + * user_id = mixpanel.get_property('user_id'); + * } + * }); + * + * @param {String} property_name The name of the super property you want to retrieve + */ + MixpanelLib.prototype.get_property = function(property_name) { + return this['persistence']['props'][property_name]; + }; + + MixpanelLib.prototype.toString = function() { + var name = this.get_config('name'); + if (name !== PRIMARY_INSTANCE_NAME) { + name = PRIMARY_INSTANCE_NAME + '.' + name; + } + return name; + }; + + MixpanelLib.prototype._event_is_disabled = function(event_name) { + return _.isBlockedUA(userAgent) || + this._flags.disable_all_events || + _.include(this.__disabled_events, event_name); + }; + + // perform some housekeeping around GDPR opt-in/out state + MixpanelLib.prototype._gdpr_init = function() { + var is_localStorage_requested = this.get_config('opt_out_tracking_persistence_type') === 'localStorage'; + + // try to convert opt-in/out cookies to localStorage if possible + if (is_localStorage_requested && _.localStorage.is_supported()) { + if (!this.has_opted_in_tracking() && this.has_opted_in_tracking({'persistence_type': 'cookie'})) { + this.opt_in_tracking({'enable_persistence': false}); + } + if (!this.has_opted_out_tracking() && this.has_opted_out_tracking({'persistence_type': 'cookie'})) { + this.opt_out_tracking({'clear_persistence': false}); + } + this.clear_opt_in_out_tracking({ + 'persistence_type': 'cookie', + 'enable_persistence': false + }); + } + + // check whether the user has already opted out - if so, clear & disable persistence + if (this.has_opted_out_tracking()) { + this._gdpr_update_persistence({'clear_persistence': true}); + + // check whether we should opt out by default + // note: we don't clear persistence here by default since opt-out default state is often + // used as an initial state while GDPR information is being collected + } else if (!this.has_opted_in_tracking() && ( + this.get_config('opt_out_tracking_by_default') || _.cookie.get('mp_optout') + )) { + _.cookie.remove('mp_optout'); + this.opt_out_tracking({ + 'clear_persistence': this.get_config('opt_out_persistence_by_default') + }); + } + }; + + /** + * Enable or disable persistence based on options + * only enable/disable if persistence is not already in this state + * @param {boolean} [options.clear_persistence] If true, will delete all data stored by the sdk in persistence and disable it + * @param {boolean} [options.enable_persistence] If true, will re-enable sdk persistence + */ + MixpanelLib.prototype._gdpr_update_persistence = function(options) { + var disabled; + if (options && options['clear_persistence']) { + disabled = true; + } else if (options && options['enable_persistence']) { + disabled = false; + } else { + return; + } + + if (!this.get_config('disable_persistence') && this['persistence'].disabled !== disabled) { + this['persistence'].set_disabled(disabled); + } + + if (disabled) { + _.each(this.request_batchers, function(batcher) { + batcher.clear(); + }); + } + }; + + // call a base gdpr function after constructing the appropriate token and options args + MixpanelLib.prototype._gdpr_call_func = function(func, options) { + options = _.extend({ + 'track': _.bind(this.track, this), + 'persistence_type': this.get_config('opt_out_tracking_persistence_type'), + 'cookie_prefix': this.get_config('opt_out_tracking_cookie_prefix'), + 'cookie_expiration': this.get_config('cookie_expiration'), + 'cross_site_cookie': this.get_config('cross_site_cookie'), + 'cross_subdomain_cookie': this.get_config('cross_subdomain_cookie'), + 'cookie_domain': this.get_config('cookie_domain'), + 'secure_cookie': this.get_config('secure_cookie'), + 'ignore_dnt': this.get_config('ignore_dnt') + }, options); + + // check if localStorage can be used for recording opt out status, fall back to cookie if not + if (!_.localStorage.is_supported()) { + options['persistence_type'] = 'cookie'; + } + + return func(this.get_config('token'), { + track: options['track'], + trackEventName: options['track_event_name'], + trackProperties: options['track_properties'], + persistenceType: options['persistence_type'], + persistencePrefix: options['cookie_prefix'], + cookieDomain: options['cookie_domain'], + cookieExpiration: options['cookie_expiration'], + crossSiteCookie: options['cross_site_cookie'], + crossSubdomainCookie: options['cross_subdomain_cookie'], + secureCookie: options['secure_cookie'], + ignoreDnt: options['ignore_dnt'] + }); + }; + + /** + * Opt the user in to data tracking and cookies/localstorage for this Mixpanel instance + * + * ### Usage + * + * // opt user in + * mixpanel.opt_in_tracking(); + * + * // opt user in with specific event name, properties, cookie configuration + * mixpanel.opt_in_tracking({ + * track_event_name: 'User opted in', + * track_event_properties: { + * 'Email': 'jdoe@example.com' + * }, + * cookie_expiration: 30, + * secure_cookie: true + * }); + * + * @param {Object} [options] A dictionary of config options to override + * @param {function} [options.track] Function used for tracking a Mixpanel event to record the opt-in action (default is this Mixpanel instance's track method) + * @param {string} [options.track_event_name=$opt_in] Event name to be used for tracking the opt-in action + * @param {Object} [options.track_properties] Set of properties to be tracked along with the opt-in action + * @param {boolean} [options.enable_persistence=true] If true, will re-enable sdk persistence + * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable + * @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this Mixpanel instance's config) + * @param {string} [options.cookie_domain] Custom cookie domain (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.cross_site_cookie] Whether the opt-in cookie is set as cross-site-enabled (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this Mixpanel instance's config) + */ + MixpanelLib.prototype.opt_in_tracking = function(options) { + options = _.extend({ + 'enable_persistence': true + }, options); + + this._gdpr_call_func(optIn, options); + this._gdpr_update_persistence(options); + }; + + /** + * Opt the user out of data tracking and cookies/localstorage for this Mixpanel instance + * + * ### Usage + * + * // opt user out + * mixpanel.opt_out_tracking(); + * + * // opt user out with different cookie configuration from Mixpanel instance + * mixpanel.opt_out_tracking({ + * cookie_expiration: 30, + * secure_cookie: true + * }); + * + * @param {Object} [options] A dictionary of config options to override + * @param {boolean} [options.delete_user=true] If true, will delete the currently identified user's profile and clear all charges after opting the user out + * @param {boolean} [options.clear_persistence=true] If true, will delete all data stored by the sdk in persistence + * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable + * @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this Mixpanel instance's config) + * @param {string} [options.cookie_domain] Custom cookie domain (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.cross_site_cookie] Whether the opt-in cookie is set as cross-site-enabled (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this Mixpanel instance's config) + */ + MixpanelLib.prototype.opt_out_tracking = function(options) { + options = _.extend({ + 'clear_persistence': true, + 'delete_user': true + }, options); + + // delete user and clear charges since these methods may be disabled by opt-out + if (options['delete_user'] && this['people'] && this['people']._identify_called()) { + this['people'].delete_user(); + this['people'].clear_charges(); + } + + this._gdpr_call_func(optOut, options); + this._gdpr_update_persistence(options); + }; + + /** + * Check whether the user has opted in to data tracking and cookies/localstorage for this Mixpanel instance + * + * ### Usage + * + * var has_opted_in = mixpanel.has_opted_in_tracking(); + * // use has_opted_in value + * + * @param {Object} [options] A dictionary of config options to override + * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable + * @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name + * @returns {boolean} current opt-in status + */ + MixpanelLib.prototype.has_opted_in_tracking = function(options) { + return this._gdpr_call_func(hasOptedIn, options); + }; + + /** + * Check whether the user has opted out of data tracking and cookies/localstorage for this Mixpanel instance + * + * ### Usage + * + * var has_opted_out = mixpanel.has_opted_out_tracking(); + * // use has_opted_out value + * + * @param {Object} [options] A dictionary of config options to override + * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable + * @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name + * @returns {boolean} current opt-out status + */ + MixpanelLib.prototype.has_opted_out_tracking = function(options) { + return this._gdpr_call_func(hasOptedOut, options); + }; + + /** + * Clear the user's opt in/out status of data tracking and cookies/localstorage for this Mixpanel instance + * + * ### Usage + * + * // clear user's opt-in/out status + * mixpanel.clear_opt_in_out_tracking(); + * + * // clear user's opt-in/out status with specific cookie configuration - should match + * // configuration used when opt_in_tracking/opt_out_tracking methods were called. + * mixpanel.clear_opt_in_out_tracking({ + * cookie_expiration: 30, + * secure_cookie: true + * }); + * + * @param {Object} [options] A dictionary of config options to override + * @param {boolean} [options.enable_persistence=true] If true, will re-enable sdk persistence + * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable + * @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this Mixpanel instance's config) + * @param {string} [options.cookie_domain] Custom cookie domain (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.cross_site_cookie] Whether the opt-in cookie is set as cross-site-enabled (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this Mixpanel instance's config) + */ + MixpanelLib.prototype.clear_opt_in_out_tracking = function(options) { + options = _.extend({ + 'enable_persistence': true + }, options); + + this._gdpr_call_func(clearOptInOut, options); + this._gdpr_update_persistence(options); + }; + + MixpanelLib.prototype.report_error = function(msg, err) { + console$1.error.apply(console$1.error, arguments); + try { + if (!err && !(msg instanceof Error)) { + msg = new Error(msg); + } + this.get_config('error_reporter')(msg, err); + } catch(err) { + console$1.error(err); + } + }; + + // EXPORTS (for closure compiler) + + // MixpanelLib Exports + MixpanelLib.prototype['init'] = MixpanelLib.prototype.init; + MixpanelLib.prototype['reset'] = MixpanelLib.prototype.reset; + MixpanelLib.prototype['disable'] = MixpanelLib.prototype.disable; + MixpanelLib.prototype['time_event'] = MixpanelLib.prototype.time_event; + MixpanelLib.prototype['track'] = MixpanelLib.prototype.track; + MixpanelLib.prototype['track_links'] = MixpanelLib.prototype.track_links; + MixpanelLib.prototype['track_forms'] = MixpanelLib.prototype.track_forms; + MixpanelLib.prototype['track_pageview'] = MixpanelLib.prototype.track_pageview; + MixpanelLib.prototype['register'] = MixpanelLib.prototype.register; + MixpanelLib.prototype['register_once'] = MixpanelLib.prototype.register_once; + MixpanelLib.prototype['unregister'] = MixpanelLib.prototype.unregister; + MixpanelLib.prototype['identify'] = MixpanelLib.prototype.identify; + MixpanelLib.prototype['alias'] = MixpanelLib.prototype.alias; + MixpanelLib.prototype['name_tag'] = MixpanelLib.prototype.name_tag; + MixpanelLib.prototype['set_config'] = MixpanelLib.prototype.set_config; + MixpanelLib.prototype['get_config'] = MixpanelLib.prototype.get_config; + MixpanelLib.prototype['get_property'] = MixpanelLib.prototype.get_property; + MixpanelLib.prototype['get_distinct_id'] = MixpanelLib.prototype.get_distinct_id; + MixpanelLib.prototype['toString'] = MixpanelLib.prototype.toString; + MixpanelLib.prototype['opt_out_tracking'] = MixpanelLib.prototype.opt_out_tracking; + MixpanelLib.prototype['opt_in_tracking'] = MixpanelLib.prototype.opt_in_tracking; + MixpanelLib.prototype['has_opted_out_tracking'] = MixpanelLib.prototype.has_opted_out_tracking; + MixpanelLib.prototype['has_opted_in_tracking'] = MixpanelLib.prototype.has_opted_in_tracking; + MixpanelLib.prototype['clear_opt_in_out_tracking'] = MixpanelLib.prototype.clear_opt_in_out_tracking; + MixpanelLib.prototype['get_group'] = MixpanelLib.prototype.get_group; + MixpanelLib.prototype['set_group'] = MixpanelLib.prototype.set_group; + MixpanelLib.prototype['add_group'] = MixpanelLib.prototype.add_group; + MixpanelLib.prototype['remove_group'] = MixpanelLib.prototype.remove_group; + MixpanelLib.prototype['track_with_groups'] = MixpanelLib.prototype.track_with_groups; + MixpanelLib.prototype['start_batch_senders'] = MixpanelLib.prototype.start_batch_senders; + MixpanelLib.prototype['stop_batch_senders'] = MixpanelLib.prototype.stop_batch_senders; + + // MixpanelPersistence Exports + MixpanelPersistence.prototype['properties'] = MixpanelPersistence.prototype.properties; + MixpanelPersistence.prototype['update_search_keyword'] = MixpanelPersistence.prototype.update_search_keyword; + MixpanelPersistence.prototype['update_referrer_info'] = MixpanelPersistence.prototype.update_referrer_info; + MixpanelPersistence.prototype['get_cross_subdomain'] = MixpanelPersistence.prototype.get_cross_subdomain; + MixpanelPersistence.prototype['clear'] = MixpanelPersistence.prototype.clear; + + + var instances = {}; + var extend_mp = function() { + // add all the sub mixpanel instances + _.each(instances, function(instance, name) { + if (name !== PRIMARY_INSTANCE_NAME) { mixpanel_master[name] = instance; } + }); + + // add private functions as _ + mixpanel_master['_'] = _; + }; + + var override_mp_init_func = function() { + // we override the snippets init function to handle the case where a + // user initializes the mixpanel library after the script loads & runs + mixpanel_master['init'] = function(token, config, name) { + if (name) { + // initialize a sub library + if (!mixpanel_master[name]) { + mixpanel_master[name] = instances[name] = create_mplib(token, config, name); + mixpanel_master[name]._loaded(); + } + return mixpanel_master[name]; + } else { + var instance = mixpanel_master; + + if (instances[PRIMARY_INSTANCE_NAME]) { + // main mixpanel lib already initialized + instance = instances[PRIMARY_INSTANCE_NAME]; + } else if (token) { + // intialize the main mixpanel lib + instance = create_mplib(token, config, PRIMARY_INSTANCE_NAME); + instance._loaded(); + instances[PRIMARY_INSTANCE_NAME] = instance; + } + + mixpanel_master = instance; + if (init_type === INIT_SNIPPET) { + window$1[PRIMARY_INSTANCE_NAME] = mixpanel_master; + } + extend_mp(); + } + }; + }; + + var add_dom_loaded_handler = function() { + // Cross browser DOM Loaded support + function dom_loaded_handler() { + // function flag since we only want to execute this once + if (dom_loaded_handler.done) { return; } + dom_loaded_handler.done = true; + + DOM_LOADED = true; + ENQUEUE_REQUESTS = false; + + _.each(instances, function(inst) { + inst._dom_loaded(); + }); + } + + function do_scroll_check() { + try { + document$1.documentElement.doScroll('left'); + } catch(e) { + setTimeout(do_scroll_check, 1); + return; + } + + dom_loaded_handler(); + } + + if (document$1.addEventListener) { + if (document$1.readyState === 'complete') { + // safari 4 can fire the DOMContentLoaded event before loading all + // external JS (including this file). you will see some copypasta + // on the internet that checks for 'complete' and 'loaded', but + // 'loaded' is an IE thing + dom_loaded_handler(); + } else { + document$1.addEventListener('DOMContentLoaded', dom_loaded_handler, false); + } + } else if (document$1.attachEvent) { + // IE + document$1.attachEvent('onreadystatechange', dom_loaded_handler); + + // check to make sure we arn't in a frame + var toplevel = false; + try { + toplevel = window$1.frameElement === null; + } catch(e) { + // noop + } + + if (document$1.documentElement.doScroll && toplevel) { + do_scroll_check(); + } + } + + // fallback handler, always will work + _.register_event(window$1, 'load', dom_loaded_handler, true); + }; + + function init_as_module() { + init_type = INIT_MODULE; + mixpanel_master = new MixpanelLib(); + + override_mp_init_func(); + mixpanel_master['init'](); + add_dom_loaded_handler(); + + return mixpanel_master; + } + + var mixpanel = init_as_module(); + + var mixpanel_cjs = mixpanel; + + var mixpanel$1 = /*#__PURE__*/_mergeNamespaces({ + __proto__: null, + default: mixpanel_cjs + }, [mixpanel_cjs]); + + const VERSION$1 = packageInfo.version; + // Needed to avoid error in CJS builds on some bundlers. + const mixpanelLib = mixpanel_cjs || mixpanel$1; + let mixpanelInstance; + /** + * Enum of mixpanel events + * @hidden + */ + const MIXPANEL_EVENT = { + VISUAL_SDK_RENDER_START: 'visual-sdk-render-start', + VISUAL_SDK_CALLED_INIT: 'visual-sdk-called-init', + VISUAL_SDK_RENDER_COMPLETE: 'visual-sdk-render-complete', + VISUAL_SDK_RENDER_FAILED: 'visual-sdk-render-failed', + VISUAL_SDK_TRIGGER: 'visual-sdk-trigger', + VISUAL_SDK_ON: 'visual-sdk-on', + VISUAL_SDK_EMBED_CREATE: 'visual-sdk-embed-create'}; + let isMixpanelInitialized = false; + let eventQueue = []; + /** + * Pushes the event with its Property key-value map to mixpanel. + * @param eventId + * @param eventProps + */ + function uploadMixpanelEvent(eventId, eventProps = {}) { + if (!isMixpanelInitialized) { + eventQueue.push({ eventId, eventProps }); + return; + } + mixpanelInstance.track(eventId, eventProps); + } + /** + * + */ + function emptyQueue() { + if (!isMixpanelInitialized) { + return; + } + eventQueue.forEach((event) => { + uploadMixpanelEvent(event.eventId, event.eventProps); + }); + eventQueue = []; + } + /** + * + * @param sessionInfo + */ + function initMixpanel(sessionInfo) { + if (!sessionInfo || !sessionInfo.mixpanelToken) { + logger$3.error(ERROR_MESSAGE.MIXPANEL_TOKEN_NOT_FOUND); + return; + } + // On a public cluster the user is anonymous, so don't set the identify to + // userGUID + const isPublicCluster = !!sessionInfo.isPublicUser; + const token = sessionInfo.mixpanelToken; + try { + if (token) { + mixpanelInstance = mixpanelLib.init(token, undefined, 'tsEmbed'); + if (!isPublicCluster) { + mixpanelInstance.identify(sessionInfo.userGUID); + } + mixpanelInstance.register_once({ + clusterId: sessionInfo.clusterId, + clusterName: sessionInfo.clusterName, + releaseVersion: sessionInfo.releaseVersion, + hostAppUrl: window?.location?.host || '', + sdkVersion: VERSION$1, + }); + isMixpanelInitialized = true; + emptyQueue(); + } + } + catch (e) { + logger$3.error('Error initializing mixpanel', e); + } + } + + var eventemitter3 = createCommonjsModule(function (module) { + + var has = Object.prototype.hasOwnProperty + , prefix = '~'; + + /** + * Constructor to create a storage for our `EE` objects. + * An `Events` instance is a plain object whose properties are event names. + * + * @constructor + * @private + */ + function Events() {} + + // + // We try to not inherit from `Object.prototype`. In some engines creating an + // instance in this way is faster than calling `Object.create(null)` directly. + // If `Object.create(null)` is not supported we prefix the event names with a + // character to make sure that the built-in object properties are not + // overridden or used as an attack vector. + // + if (Object.create) { + Events.prototype = Object.create(null); + + // + // This hack is needed because the `__proto__` property is still inherited in + // some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5. + // + if (!new Events().__proto__) prefix = false; + } + + /** + * Representation of a single event listener. + * + * @param {Function} fn The listener function. + * @param {*} context The context to invoke the listener with. + * @param {Boolean} [once=false] Specify if the listener is a one-time listener. + * @constructor + * @private + */ + function EE(fn, context, once) { + this.fn = fn; + this.context = context; + this.once = once || false; + } + + /** + * Add a listener for a given event. + * + * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. + * @param {(String|Symbol)} event The event name. + * @param {Function} fn The listener function. + * @param {*} context The context to invoke the listener with. + * @param {Boolean} once Specify if the listener is a one-time listener. + * @returns {EventEmitter} + * @private + */ + function addListener(emitter, event, fn, context, once) { + if (typeof fn !== 'function') { + throw new TypeError('The listener must be a function'); + } + + var listener = new EE(fn, context || emitter, once) + , evt = prefix ? prefix + event : event; + + if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++; + else if (!emitter._events[evt].fn) emitter._events[evt].push(listener); + else emitter._events[evt] = [emitter._events[evt], listener]; + + return emitter; + } + + /** + * Clear event by name. + * + * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. + * @param {(String|Symbol)} evt The Event name. + * @private + */ + function clearEvent(emitter, evt) { + if (--emitter._eventsCount === 0) emitter._events = new Events(); + else delete emitter._events[evt]; + } + + /** + * Minimal `EventEmitter` interface that is molded against the Node.js + * `EventEmitter` interface. + * + * @constructor + * @public + */ + function EventEmitter() { + this._events = new Events(); + this._eventsCount = 0; + } + + /** + * Return an array listing the events for which the emitter has registered + * listeners. + * + * @returns {Array} + * @public + */ + EventEmitter.prototype.eventNames = function eventNames() { + var names = [] + , events + , name; + + if (this._eventsCount === 0) return names; + + for (name in (events = this._events)) { + if (has.call(events, name)) names.push(prefix ? name.slice(1) : name); + } + + if (Object.getOwnPropertySymbols) { + return names.concat(Object.getOwnPropertySymbols(events)); + } + + return names; + }; + + /** + * Return the listeners registered for a given event. + * + * @param {(String|Symbol)} event The event name. + * @returns {Array} The registered listeners. + * @public + */ + EventEmitter.prototype.listeners = function listeners(event) { + var evt = prefix ? prefix + event : event + , handlers = this._events[evt]; + + if (!handlers) return []; + if (handlers.fn) return [handlers.fn]; + + for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) { + ee[i] = handlers[i].fn; + } + + return ee; + }; + + /** + * Return the number of listeners listening to a given event. + * + * @param {(String|Symbol)} event The event name. + * @returns {Number} The number of listeners. + * @public + */ + EventEmitter.prototype.listenerCount = function listenerCount(event) { + var evt = prefix ? prefix + event : event + , listeners = this._events[evt]; + + if (!listeners) return 0; + if (listeners.fn) return 1; + return listeners.length; + }; + + /** + * Calls each of the listeners registered for a given event. + * + * @param {(String|Symbol)} event The event name. + * @returns {Boolean} `true` if the event had listeners, else `false`. + * @public + */ + EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { + var evt = prefix ? prefix + event : event; + + if (!this._events[evt]) return false; + + var listeners = this._events[evt] + , len = arguments.length + , args + , i; + + if (listeners.fn) { + if (listeners.once) this.removeListener(event, listeners.fn, undefined, true); + + switch (len) { + case 1: return listeners.fn.call(listeners.context), true; + case 2: return listeners.fn.call(listeners.context, a1), true; + case 3: return listeners.fn.call(listeners.context, a1, a2), true; + case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true; + case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true; + case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true; + } + + for (i = 1, args = new Array(len -1); i < len; i++) { + args[i - 1] = arguments[i]; + } + + listeners.fn.apply(listeners.context, args); + } else { + var length = listeners.length + , j; + + for (i = 0; i < length; i++) { + if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true); + + switch (len) { + case 1: listeners[i].fn.call(listeners[i].context); break; + case 2: listeners[i].fn.call(listeners[i].context, a1); break; + case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break; + case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break; + default: + if (!args) for (j = 1, args = new Array(len -1); j < len; j++) { + args[j - 1] = arguments[j]; + } + + listeners[i].fn.apply(listeners[i].context, args); + } + } + } + + return true; + }; + + /** + * Add a listener for a given event. + * + * @param {(String|Symbol)} event The event name. + * @param {Function} fn The listener function. + * @param {*} [context=this] The context to invoke the listener with. + * @returns {EventEmitter} `this`. + * @public + */ + EventEmitter.prototype.on = function on(event, fn, context) { + return addListener(this, event, fn, context, false); + }; + + /** + * Add a one-time listener for a given event. + * + * @param {(String|Symbol)} event The event name. + * @param {Function} fn The listener function. + * @param {*} [context=this] The context to invoke the listener with. + * @returns {EventEmitter} `this`. + * @public + */ + EventEmitter.prototype.once = function once(event, fn, context) { + return addListener(this, event, fn, context, true); + }; + + /** + * Remove the listeners of a given event. + * + * @param {(String|Symbol)} event The event name. + * @param {Function} fn Only remove the listeners that match this function. + * @param {*} context Only remove the listeners that have this context. + * @param {Boolean} once Only remove one-time listeners. + * @returns {EventEmitter} `this`. + * @public + */ + EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) { + var evt = prefix ? prefix + event : event; + + if (!this._events[evt]) return this; + if (!fn) { + clearEvent(this, evt); + return this; + } + + var listeners = this._events[evt]; + + if (listeners.fn) { + if ( + listeners.fn === fn && + (!once || listeners.once) && + (!context || listeners.context === context) + ) { + clearEvent(this, evt); + } + } else { + for (var i = 0, events = [], length = listeners.length; i < length; i++) { + if ( + listeners[i].fn !== fn || + (once && !listeners[i].once) || + (context && listeners[i].context !== context) + ) { + events.push(listeners[i]); + } + } + + // + // Reset the array, or remove it completely if we have no more listeners. + // + if (events.length) this._events[evt] = events.length === 1 ? events[0] : events; + else clearEvent(this, evt); + } + + return this; + }; + + /** + * Remove all listeners, or those of the specified event. + * + * @param {(String|Symbol)} [event] The event name. + * @returns {EventEmitter} `this`. + * @public + */ + EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) { + var evt; + + if (event) { + evt = prefix ? prefix + event : event; + if (this._events[evt]) clearEvent(this, evt); + } else { + this._events = new Events(); + this._eventsCount = 0; + } + + return this; + }; + + // + // Alias methods names because people roll like that. + // + EventEmitter.prototype.off = EventEmitter.prototype.removeListener; + EventEmitter.prototype.addListener = EventEmitter.prototype.on; + + // + // Expose the prefix. + // + EventEmitter.prefixed = prefix; + + // + // Allow `EventEmitter` to be imported as module namespace. + // + EventEmitter.EventEmitter = EventEmitter; + + // + // Expose the module. + // + { + module.exports = EventEmitter; + } + }); + + var ReportType; + (function (ReportType) { + ReportType["CSP_VIOLATION"] = "csp-violation"; + ReportType["DEPRECATION"] = "deprecation"; + ReportType["INTERVENTION"] = "intervention"; + })(ReportType || (ReportType = {})); + let globalObserver = null; + /** + * Register a global ReportingObserver to capture all unhandled errors + * @param overrideExisting boolean to override existing observer + * @returns ReportingObserver | null + */ + function registerReportingObserver(overrideExisting = false) { + if (!(window.ReportingObserver)) { + logger$3.warn(ERROR_MESSAGE.MISSING_REPORTING_OBSERVER); + return null; + } + if (overrideExisting) { + resetGlobalReportingObserver(); + } + if (globalObserver) { + return globalObserver; + } + const embedConfig = getEmbedConfig(); + globalObserver = new ReportingObserver((reports) => { + reports.forEach((report) => { + const { type, url, body } = report; + const reportBody = body; + const isThoughtSpotHost = url + && url.startsWith(embedConfig.thoughtSpotHost); + const isFrameHostError = type === ReportType.CSP_VIOLATION + && reportBody.effectiveDirective === 'frame-ancestors'; + if (isThoughtSpotHost && isFrameHostError) { + if (!embedConfig.suppressErrorAlerts) { + alert(ERROR_MESSAGE.CSP_VIOLATION_ALERT); + } + logger$3.error(ERROR_MESSAGE.CSP_FRAME_HOST_VIOLATION_LOG_MESSAGE); + } + }); + }, { buffered: true }); + globalObserver.observe(); + return globalObserver; + } + /** + * Resets the global ReportingObserver + */ + function resetGlobalReportingObserver() { + if (globalObserver) + globalObserver.disconnect(); + globalObserver = null; + } + + /** + * + * @param url + * @param options + */ + function tokenizedFailureLoggedFetch(url, options = {}) { + return tokenizedFetch(url, options).then(async (r) => { + if (!r.ok && r.type !== 'opaqueredirect' && r.type !== 'opaque') { + logger$3.error(`Failed to fetch ${url}`, await r.text?.()); + } + return r; + }); + } + /** + * Fetches the session info from the ThoughtSpot server. + * @param thoughtspotHost + * @returns {Promise} + * @example + * ```js + * const response = await sessionInfoService(); + * ``` + */ + async function fetchPreauthInfoService(thoughtspotHost) { + const sessionInfoPath = `${thoughtspotHost}${EndPoints.PREAUTH_INFO}`; + const handleError = (e) => { + const error = new Error(`Failed to fetch auth info: ${e.message || e.statusText}`); + error.status = e.status; // Attach the status code to the error object + throw error; + }; + try { + const response = await tokenizedFailureLoggedFetch(sessionInfoPath); + return response; + } + catch (e) { + handleError(e); + return null; + } + } + /** + * Fetches the session info from the ThoughtSpot server. + * @param thoughtspotHost + * @returns {Promise} + * @example + * ```js + * const response = await sessionInfoService(); + * ``` + */ + async function fetchSessionInfoService(thoughtspotHost) { + const sessionInfoPath = `${thoughtspotHost}${EndPoints.SESSION_INFO}`; + const response = await tokenizedFailureLoggedFetch(sessionInfoPath); + if (!response.ok) { + throw new Error(`Failed to fetch session info: ${response.statusText}`); + } + const data = await response.json(); + return data; + } + /** + * Is active service to check if the user is logged in. + * @param thoughtSpotHost + * @version SDK: 1.28.4 | ThoughtSpot: * + */ + async function isActiveService(thoughtSpotHost) { + const isActiveUrl = `${thoughtSpotHost}${EndPoints.IS_ACTIVE}`; + try { + const res = await tokenizedFetch(isActiveUrl, { + credentials: 'include', + }); + return res.ok; + } + catch (e) { + logger$3.warn(`Is Logged In Service failed : ${e.message}`); + } + return false; + } + + let sessionInfo = null; + let preauthInfo = null; + /** + * Processes the session info response and returns the session info object. + * @param preauthInfoResp {any} Response from the session info API. + * @returns {PreauthInfo} The session info object. + * @example ```js + * const preauthInfoResp = await fetch(sessionInfoPath); + * const sessionInfo = await formatPreauthInfo(preauthInfoResp); + * console.log(sessionInfo); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + */ + const formatPreauthInfo = async (preauthInfoResp) => { + try { + // Convert Headers to a plain object + const headers = {}; + preauthInfoResp?.headers?.forEach((value, key) => { + headers[key] = value; + }); + const data = await preauthInfoResp.json(); + return { + ...data, + status: 200, + headers, + }; + } + catch (error) { + return null; + } + }; + /** + * Returns the session info object and caches it for future use. + * Once fetched the session info object is cached and returned from the cache on + * subsequent calls. + * @example ```js + * const preauthInfo = await getPreauthInfo(); + * console.log(preauthInfo); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + * @returns {Promise} The session info object. + */ + async function getPreauthInfo(allowCache = true) { + if (!allowCache || !preauthInfo) { + try { + const host = getEmbedConfig().thoughtSpotHost; + const sessionResponse = await fetchPreauthInfoService(host); + const processedPreauthInfo = await formatPreauthInfo(sessionResponse); + preauthInfo = processedPreauthInfo; + } + catch (error) { + return null; + } + } + return preauthInfo; + } + /** + * Returns the cached session info object and caches it for future use. + * Once fetched the session info object is cached and returned from the cache on + * subsequent calls. + * This cache is cleared when inti is called OR resetCachedSessionInfo is called. + * @example ```js + * const sessionInfo = await getSessionInfo(); + * console.log(sessionInfo); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + * @returns {Promise} The session info object. + */ + async function getSessionInfo() { + if (!sessionInfo) { + const host = getEmbedConfig().thoughtSpotHost; + const sessionResponse = await fetchSessionInfoService(host); + const processedSessionInfo = getSessionDetails(sessionResponse); + sessionInfo = processedSessionInfo; + } + return sessionInfo; + } + /** + * Processes the session info response and returns the session info object. + * @param sessionInfoResp {any} Response from the session info API. + * @returns {SessionInfo} The session info object. + * @example ```js + * const sessionInfoResp = await fetch(sessionInfoPath); + * const sessionInfo = getSessionDetails(sessionInfoResp); + * console.log(sessionInfo); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + */ + const getSessionDetails = (sessionInfoResp) => { + const devMixpanelToken = sessionInfoResp.configInfo.mixpanelConfig.devSdkKey; + const prodMixpanelToken = sessionInfoResp.configInfo.mixpanelConfig.prodSdkKey; + const mixpanelToken = sessionInfoResp.configInfo.mixpanelConfig.production + ? prodMixpanelToken + : devMixpanelToken; + return { + userGUID: sessionInfoResp.userGUID, + mixpanelToken, + isPublicUser: sessionInfoResp.configInfo.isPublicUser, + releaseVersion: sessionInfoResp.releaseVersion, + clusterId: sessionInfoResp.configInfo.selfClusterId, + clusterName: sessionInfoResp.configInfo.selfClusterName, + ...sessionInfoResp, + }; + }; + /** + * Resets the cached session info object and forces a new fetch on the next call. + * @example ```js + * resetCachedSessionInfo(); + * const sessionInfo = await getSessionInfo(); + * console.log(sessionInfo); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + * @returns {void} + */ + function resetCachedSessionInfo() { + sessionInfo = null; + } + /** + * Resets the cached preauth info object and forces a new fetch on the next call. + * @example ```js + * resetCachedPreauthInfo(); + * const preauthInfo = await getPreauthInfo(); + * console.log(preauthInfo); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + * @returns {void} + */ + function resetCachedPreauthInfo() { + preauthInfo = null; + } + + /** + * This function resets all the services that are cached in the SDK. + * This is to be called when the user logs out of the application and also + * when init is called again. + * @version SDK: 1.30.2 | ThoughtSpot: * + */ + function resetAllCachedServices() { + resetCachedAuthToken(); + resetCachedSessionInfo(); + resetCachedPreauthInfo(); + } + + let loggedInStatus = false; + let samlAuthWindow = null; + let samlCompletionPromise = null; + let releaseVersion = ''; + const SSO_REDIRECTION_MARKER_GUID = '5e16222e-ef02-43e9-9fbd-24226bf3ce5b'; + /** + * Enum for auth failure types. + * This value is passed to the listener for {@link AuthStatus.FAILURE}. + * @group Authentication / Init + */ + var AuthFailureType; + (function (AuthFailureType) { + /** + * Authentication failed in the SDK authentication flow. + * + * Emitted when `init()` or auto-authentication cannot establish a logged-in session. + * For example, this can happen because of an invalid token, an auth request failure, + * or an auth promise rejection. + */ + AuthFailureType["SDK"] = "SDK"; + /** + * Browser cookie access is blocked for the embedded app. + * + * Emitted when the iframe reports that required cookies + * cannot be read or sent, commonly due to third-party cookie restrictions. + */ + AuthFailureType["NO_COOKIE_ACCESS"] = "NO_COOKIE_ACCESS"; + /** + * The current authentication token or session has expired. + * + * Emitted when the embed receives an auth-expiry signal and starts auth refresh + * handling. + */ + AuthFailureType["EXPIRY"] = "EXPIRY"; + /** + * A generic authentication failure that does not match a more specific type. + * + * Emitted as a fallback for app-reported auth failures in standard auth flows. + */ + AuthFailureType["OTHER"] = "OTHER"; + /** + * The user session timed out due to inactivity. + * + * Emitted when the app reports an idle-session timeout. + */ + AuthFailureType["IDLE_SESSION_TIMEOUT"] = "IDLE_SESSION_TIMEOUT"; + /** + * The app reports that the user is unauthenticated. + * + * Used primarily to classify unauthenticated failures in Embedded SSO flows. + */ + AuthFailureType["UNAUTHENTICATED_FAILURE"] = "UNAUTHENTICATED_FAILURE"; + })(AuthFailureType || (AuthFailureType = {})); + /** + * Enum for auth status emitted by the emitter returned from {@link init}. + * @group Authentication / Init + */ + var AuthStatus; + (function (AuthStatus) { + /** + * Emits when the SDK fails to authenticate. + */ + AuthStatus["FAILURE"] = "FAILURE"; + /** + * Emits when the SDK authentication step completes + * successfully (e.g., token exchange, cookie set). + * This fires before any iframe is rendered. Use + * this to know that auth passed and it is safe to + * proceed with rendering. The callback receives no + * arguments. + * @example + * ```js + * const authEE = init({ ... }); + * authEE.on(AuthStatus.SDK_SUCCESS, () => { + * // Auth done, iframe not loaded yet + * }); + * ``` + */ + AuthStatus["SDK_SUCCESS"] = "SDK_SUCCESS"; + /** + * @hidden + * Emits when iframe is loaded and session + * information is available. + */ + AuthStatus["SESSION_INFO_SUCCESS"] = "SESSION_INFO_SUCCESS"; + /** + * Emits when the ThoughtSpot app inside the + * embedded iframe confirms its session is active. + * This fires after the iframe loads and sends back an `AuthInit` event. + * @param sessionInfo Information about the user session, with details like `userGUID`. + * @see EmbedEvent.AuthInit + * @example + * ```js + * const authEE = init({ ... }); + * authEE.on(AuthStatus.SUCCESS, (sessionInfo) => { + * // App is loaded and authenticated + * console.log(sessionInfo.userGUID); + * }); + * ``` + */ + AuthStatus["SUCCESS"] = "SUCCESS"; + /** + * Emits when a user logs out + */ + AuthStatus["LOGOUT"] = "LOGOUT"; + /** + * Emitted when inPopup is true in the SAMLRedirect flow and the + * popup is waiting to be triggered either programmatically + * or by the trigger button. + * @version SDK: 1.19.0 + */ + AuthStatus["WAITING_FOR_POPUP"] = "WAITING_FOR_POPUP"; + /** + * Emitted when the SAML popup is closed without authentication + */ + AuthStatus["SAML_POPUP_CLOSED_NO_AUTH"] = "SAML_POPUP_CLOSED_NO_AUTH"; + })(AuthStatus || (AuthStatus = {})); + /** + * Events which can be triggered on the emitter returned from {@link init}. + * @group Authentication / Init + */ + var AuthEvent; + (function (AuthEvent) { + /** + * Manually trigger the SSO popup. This is useful when + * authStatus is SAMLRedirect/OIDCRedirect and inPopup is set to true + */ + AuthEvent["TRIGGER_SSO_POPUP"] = "TRIGGER_SSO_POPUP"; + })(AuthEvent || (AuthEvent = {})); + let authEE; + /** + * + * @param eventEmitter + */ + function setAuthEE(eventEmitter) { + authEE = eventEmitter; + } + /** + * + */ + function notifyAuthSDKSuccess() { + if (!authEE) { + logger$3.error(ERROR_MESSAGE.SDK_NOT_INITIALIZED); + return; + } + authEE.emit(AuthStatus.SDK_SUCCESS); + } + /** + * + */ + async function notifyAuthSuccess() { + if (!authEE) { + logger$3.error(ERROR_MESSAGE.SDK_NOT_INITIALIZED); + return; + } + try { + getPreauthInfo(); + const sessionInfo = await getSessionInfo(); + authEE.emit(AuthStatus.SUCCESS, sessionInfo); + } + catch (e) { + logger$3.error(ERROR_MESSAGE.SESSION_INFO_FAILED); + } + } + /** + * + * @param failureType + */ + function notifyAuthFailure(failureType) { + if (!authEE) { + logger$3.error(ERROR_MESSAGE.SDK_NOT_INITIALIZED); + return; + } + authEE.emit(AuthStatus.FAILURE, failureType); + } + /** + * + */ + function notifyLogout() { + if (!authEE) { + logger$3.error(ERROR_MESSAGE.SDK_NOT_INITIALIZED); + return; + } + authEE.emit(AuthStatus.LOGOUT); + } + /** + * Check if we are logged into the ThoughtSpot cluster + * @param thoughtSpotHost The ThoughtSpot cluster hostname or IP + */ + async function isLoggedIn(thoughtSpotHost) { + try { + const response = await isActiveService(thoughtSpotHost); + return response; + } + catch (e) { + return false; + } + } + /** + * Services to be called after the login is successful, + * This should be called after the cookie is set for cookie auth or + * after the token is set for cookieless. + * @return {Promise} + * @example + * ```js + * await postLoginService(); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + */ + async function postLoginService() { + try { + getPreauthInfo(); + const sessionInfo = await getSessionInfo(); + releaseVersion = sessionInfo.releaseVersion; + const embedConfig = getEmbedConfig(); + if (!embedConfig.disableSDKTracking) { + initMixpanel(sessionInfo); + } + } + catch (e) { + logger$3.error('Post login services failed.', e.message, e); + } + } + /** + * Return releaseVersion if available + */ + function getReleaseVersion() { + return releaseVersion; + } + /** + * Check if we are stuck at the SSO redirect URL + */ + function isAtSSORedirectUrl() { + return window.location.href.indexOf(getSSOMarker(SSO_REDIRECTION_MARKER_GUID)) >= 0; + } + /** + * Remove the SSO redirect URL marker + */ + function removeSSORedirectUrlMarker() { + // Note (sunny): This will leave a # around even if it was not in the URL + // to begin with. Trying to remove the hash by changing window.location will + // reload the page which we don't want. We'll live with adding an + // unnecessary hash to the parent page URL until we find any use case where + // that creates an issue. + // Replace any occurrences of ?ssoMarker=guid or &ssoMarker=guid. + let updatedHash = window.location.hash.replace(`?${getSSOMarker(SSO_REDIRECTION_MARKER_GUID)}`, ''); + updatedHash = updatedHash.replace(`&${getSSOMarker(SSO_REDIRECTION_MARKER_GUID)}`, ''); + window.location.hash = updatedHash; + } + /** + * Perform token based authentication + * @param embedConfig The embed configuration + */ + const doTokenAuth = async (embedConfig) => { + const { thoughtSpotHost, username, authEndpoint, getAuthToken, } = embedConfig; + if (!authEndpoint && !getAuthToken) { + throw new Error('Either auth endpoint or getAuthToken function must be provided'); + } + loggedInStatus = await isLoggedIn(thoughtSpotHost); + if (!loggedInStatus) { + let authToken; + try { + authToken = await getAuthenticationToken(embedConfig); + } + catch (e) { + loggedInStatus = false; + throw e; + } + let resp; + try { + resp = await fetchAuthPostService(thoughtSpotHost, username, authToken); + } + catch (e) { + resp = await fetchAuthService(thoughtSpotHost, username, authToken); + } + // token login issues a 302 when successful + loggedInStatus = resp.ok || resp.type === 'opaqueredirect'; + if (loggedInStatus && embedConfig.detectCookieAccessSlow) { + // When 3rd party cookie access is blocked, this will fail because + // cookies will not be sent with the call. + loggedInStatus = await isLoggedIn(thoughtSpotHost); + } + } + return loggedInStatus; + }; + /** + * Validate embedConfig parameters required for cookielessTokenAuth + * @param embedConfig The embed configuration + */ + const doCookielessTokenAuth = async (embedConfig) => { + const { authEndpoint, getAuthToken } = embedConfig; + if (!authEndpoint && !getAuthToken) { + throw new Error('Either auth endpoint or getAuthToken function must be provided'); + } + let authSuccess = false; + try { + const authToken = await getAuthenticationToken(embedConfig); + if (authToken) { + authSuccess = true; + } + } + catch { + authSuccess = false; + } + return authSuccess; + }; + /** + * Perform basic authentication to the ThoughtSpot cluster using the cluster + * credentials. + * + * Warning: This feature is primarily intended for developer testing. It is + * strongly advised not to use this authentication method in production. + * @param embedConfig The embed configuration + */ + const doBasicAuth = async (embedConfig) => { + const { thoughtSpotHost, username, password } = embedConfig; + const loggedIn = await isLoggedIn(thoughtSpotHost); + if (!loggedIn) { + const response = await fetchBasicAuthService(thoughtSpotHost, username, password); + loggedInStatus = response.ok; + if (embedConfig.detectCookieAccessSlow) { + loggedInStatus = await isLoggedIn(thoughtSpotHost); + } + } + else { + loggedInStatus = true; + } + return loggedInStatus; + }; + /** + * + * @param ssoURL + * @param triggerContainer + * @param triggerText + */ + async function samlPopupFlow(ssoURL, triggerContainer, triggerText) { + let popupClosedCheck; + const openPopup = () => { + if (samlAuthWindow === null || samlAuthWindow.closed) { + samlAuthWindow = window.open(ssoURL, '_blank', 'location=no,height=570,width=520,scrollbars=yes,status=yes'); + if (samlAuthWindow) { + popupClosedCheck = setInterval(() => { + if (samlAuthWindow.closed) { + clearInterval(popupClosedCheck); + if (samlCompletionPromise && !samlCompletionResolved) { + authEE?.emit(AuthStatus.SAML_POPUP_CLOSED_NO_AUTH); + } + } + }, 500); + } + } + else { + samlAuthWindow.focus(); + } + }; + let samlCompletionResolved = false; + authEE?.emit(AuthStatus.WAITING_FOR_POPUP); + const containerEl = getDOMNode(triggerContainer); + if (containerEl) { + containerEl.innerHTML = ''; + const authElem = document.getElementById('ts-auth-btn'); + authElem.textContent = triggerText; + authElem.addEventListener('click', openPopup, { once: true }); + } + samlCompletionPromise = samlCompletionPromise || new Promise((resolve, reject) => { + window.addEventListener('message', (e) => { + if (e.data.type === exports.EmbedEvent.SAMLComplete) { + if (e.data.accessToken) { + const decodedToken = decodeURIComponent(e.data.accessToken); + storeAuthTokenInCache(decodedToken); + } + samlCompletionResolved = true; + if (popupClosedCheck) { + clearInterval(popupClosedCheck); + } + e.source.close(); + resolve(); + } + }); + }); + authEE?.once(AuthEvent.TRIGGER_SSO_POPUP, openPopup); + return samlCompletionPromise; + } + /** + * Perform SAML authentication + * @param embedConfig The embed configuration + * @param ssoEndPoint + */ + const doSSOAuth = async (embedConfig, ssoEndPoint) => { + const { thoughtSpotHost } = embedConfig; + const loggedIn = await isLoggedIn(thoughtSpotHost); + if (loggedIn) { + if (isAtSSORedirectUrl()) { + removeSSORedirectUrlMarker(); + } + loggedInStatus = true; + return; + } + // we have already tried authentication and it did not succeed, restore + // the current URL to the original one and invoke the callback. + if (isAtSSORedirectUrl()) { + removeSSORedirectUrlMarker(); + loggedInStatus = false; + return; + } + const ssoURL = `${thoughtSpotHost}${ssoEndPoint}`; + if (embedConfig.inPopup) { + await samlPopupFlow(ssoURL, embedConfig.authTriggerContainer, embedConfig.authTriggerText); + const cachedToken = getCacheAuthToken(); + if (cachedToken) { + loggedInStatus = true; + } + else { + loggedInStatus = await isLoggedIn(thoughtSpotHost); + } + return; + } + window.location.href = ssoURL; + }; + const doSamlAuth = async (embedConfig) => { + const { thoughtSpotHost } = embedConfig; + // redirect for SSO, when the SSO authentication is done, this page will be + // loaded again and the same JS will execute again. + const ssoRedirectUrl = embedConfig.inPopup + ? `${thoughtSpotHost}/v2/#/embed/saml-complete` + : getRedirectUrl(window.location.href, SSO_REDIRECTION_MARKER_GUID, embedConfig.redirectPath); + // bring back the page to the same URL + const ssoEndPoint = `${EndPoints.SAML_LOGIN_TEMPLATE(encodeURIComponent(ssoRedirectUrl))}`; + await doSSOAuth(embedConfig, ssoEndPoint); + return loggedInStatus; + }; + const doOIDCAuth = async (embedConfig) => { + const { thoughtSpotHost } = embedConfig; + // redirect for SSO, when the SSO authentication is done, this page will be + // loaded again and the same JS will execute again. + const ssoRedirectUrl = embedConfig.noRedirect || embedConfig.inPopup + ? `${thoughtSpotHost}/v2/#/embed/saml-complete` + : getRedirectUrl(window.location.href, SSO_REDIRECTION_MARKER_GUID, embedConfig.redirectPath); + // bring back the page to the same URL + const baseEndpoint = `${EndPoints.OIDC_LOGIN_TEMPLATE(encodeURIComponent(ssoRedirectUrl))}`; + const ssoEndPoint = `${baseEndpoint}${baseEndpoint.includes('?') ? '&' : '?'}forceSAMLAutoRedirect=true`; + await doSSOAuth(embedConfig, ssoEndPoint); + return loggedInStatus; + }; + /** + * Perform authentication on the ThoughtSpot cluster + * @param embedConfig The embed configuration + */ + const authenticate = async (embedConfig) => { + const { authType } = embedConfig; + switch (authType) { + case AuthType.SSO: + case AuthType.SAMLRedirect: + case AuthType.SAML: + return doSamlAuth(embedConfig); + case AuthType.OIDC: + case AuthType.OIDCRedirect: + return doOIDCAuth(embedConfig); + case AuthType.AuthServer: + case AuthType.TrustedAuthToken: + return doTokenAuth(embedConfig); + case AuthType.TrustedAuthTokenCookieless: + return doCookielessTokenAuth(embedConfig); + case AuthType.Basic: + return doBasicAuth(embedConfig); + default: + return Promise.resolve(true); + } + }; + + if (typeof Promise.withResolvers === 'undefined') { + Promise.withResolvers = () => { + let resolve; + let reject; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; + }; + } + + /** + * Reloads the ThoughtSpot iframe. + * @param iFrame + */ + const reload = (iFrame) => { + const src = iFrame.src; + iFrame.src = ''; + setTimeout(() => { + iFrame.src = src; + }, 100); + }; + /** + * Post iframe message. + * @param iFrame + * @param message + * @param message.type + * @param message.data + * @param message.context + * @param thoughtSpotHost + * @param channel + */ + function postIframeMessage(iFrame, message, thoughtSpotHost, channel) { + return iFrame.contentWindow?.postMessage(message, thoughtSpotHost, [channel?.port2]); + } + const TRIGGER_TIMEOUT = 30000; + /** + * + * @param iFrame + * @param messageType + * @param thoughtSpotHost + * @param data + * @param context + */ + function processTrigger(iFrame, messageType, thoughtSpotHost, data, context) { + return new Promise((res, rej) => { + if (messageType === exports.HostEvent.Reload) { + reload(iFrame); + return res(null); + } + if (messageType === exports.HostEvent.Present) { + const embedConfig = getEmbedConfig(); + const disableFullscreenPresentation = embedConfig?.disableFullscreenPresentation ?? true; + if (!disableFullscreenPresentation) { + handlePresentEvent(iFrame); + } + else { + logger$3.warn('Fullscreen presentation mode is disabled. Set disableFullscreenPresentation: false to enable this feature.'); + } + } + const channel = new MessageChannel(); + channel.port1.onmessage = ({ data: responseData }) => { + channel.port1.close(); + const error = responseData?.error || responseData?.data?.error; + if (error) { + rej(error); + } + else { + res(responseData); + } + }; + // Close the messageChannel and resolve the promise if timeout. + setTimeout(() => { + channel.port1.close(); + res(new Error(ERROR_MESSAGE.TRIGGER_TIMED_OUT)); + }, TRIGGER_TIMEOUT); + return postIframeMessage(iFrame, { type: messageType, data, context }, thoughtSpotHost, channel); + }); + } + + /** + * Copyright (c) 2022 + * + * Base classes + * @summary Base classes + * @author Ayon Ghosh + */ + const CONFIG_DEFAULTS = { + loginFailedMessage: 'Not logged in', + authTriggerText: 'Authorize', + authType: AuthType.None, + logLevel: exports.LogLevel.ERROR, + waitForCleanupOnDestroy: false, + cleanupTimeout: 5000, + }; + let authPromise; + const getAuthPromise = () => authPromise; + /** + * Perform authentication on the ThoughtSpot app as applicable. + */ + const handleAuth = () => { + authPromise = authenticate(getEmbedConfig()); + authPromise.then((isLoggedIn) => { + if (!isLoggedIn) { + notifyAuthFailure(AuthFailureType.SDK); + } + else { + // Post login service is called after successful login. + postLoginService(); + notifyAuthSDKSuccess(); + } + }, () => { + notifyAuthFailure(AuthFailureType.SDK); + }); + return authPromise; + }; + const hostUrlToFeatureUrl = { + [PrefetchFeatures.SearchEmbed]: (url, flags) => `${url}v2/?${flags}#/embed/answer`, + [PrefetchFeatures.LiveboardEmbed]: (url, flags) => `${url}?${flags}`, + [PrefetchFeatures.FullApp]: (url, flags) => `${url}?${flags}`, + [PrefetchFeatures.VizEmbed]: (url, flags) => `${url}?${flags}`, + }; + /** + * Prefetches static resources from the specified URL. Web browsers can then cache the + * prefetched resources and serve them from the user's local disk to provide faster access + * to your app. + * @param url The URL provided for prefetch + * @param prefetchFeatures Specify features which needs to be prefetched. + * @param additionalFlags This can be used to add any URL flag. + * @version SDK: 1.4.0 | ThoughtSpot: ts7.sep.cl, 7.2.1 + * @group Global methods + */ + const prefetch = (url, prefetchFeatures, additionalFlags) => { + if (url === '') { + logger$3.warn('The prefetch method does not have a valid URL'); + } + else { + const features = [PrefetchFeatures.FullApp]; + let hostUrl = url || getEmbedConfig().thoughtSpotHost; + const prefetchFlags = { + [Param.EmbedApp]: true, + ...getEmbedConfig()?.additionalFlags, + ...additionalFlags, + }; + hostUrl = hostUrl[hostUrl.length - 1] === '/' ? hostUrl : `${hostUrl}/`; + Array.from(new Set(features + .map((feature) => hostUrlToFeatureUrl[feature](hostUrl, getQueryParamString(prefetchFlags))))) + .forEach((prefetchUrl, index) => { + const iFrame = document.createElement('iframe'); + iFrame.src = prefetchUrl; + iFrame.style.width = '0'; + iFrame.style.height = '0'; + iFrame.style.border = '0'; + // Make it 'fixed' to keep it in a different stacking + // context. This should solve the focus behaviours inside + // the iframe from interfering with main body. + iFrame.style.position = 'fixed'; + // Push it out of viewport. + iFrame.style.top = '100vh'; + iFrame.style.left = '100vw'; + iFrame.classList.add('prefetchIframe'); + iFrame.classList.add(`prefetchIframeNum-${index}`); + document.body.appendChild(iFrame); + }); + } + }; + /** + * + * @param embedConfig + */ + function sanity(embedConfig) { + if (embedConfig.thoughtSpotHost === undefined) { + throw new Error('ThoughtSpot host not provided'); + } + if (embedConfig.authType === AuthType.TrustedAuthToken) { + if (!embedConfig.authEndpoint && typeof embedConfig.getAuthToken !== 'function') { + throw new Error('Trusted auth should provide either authEndpoint or getAuthToken'); + } + } + } + /** + * + * @param embedConfig + */ + function backwardCompat(embedConfig) { + const newConfig = { ...embedConfig }; + if (embedConfig.noRedirect !== undefined && embedConfig.inPopup === undefined) { + newConfig.inPopup = embedConfig.noRedirect; + } + return newConfig; + } + const initFlagKey = 'initFlagKey'; + const createAndSetInitPromise = () => { + if (isWindowUndefined()) + return; + const { promise: initPromise, resolve: initPromiseResolve, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + } = Promise.withResolvers(); + const initFlagStore = { + initPromise, + isInitCalled: false, + isInitCompleted: false, + initPromiseResolve, + }; + storeValueInWindow(initFlagKey, initFlagStore, { + // In case of diff imports the promise might be already set + ignoreIfAlreadyExists: true, + }); + initPromise.finally(() => { + const curVal = getValueFromWindow(initFlagKey); + curVal.isInitCompleted = true; + storeValueInWindow(initFlagKey, curVal); + }); + }; + createAndSetInitPromise(); + const getInitPromise = () => getValueFromWindow(initFlagKey)?.initPromise; + const getIsInitCompleted = () => getValueFromWindow(initFlagKey)?.isInitCompleted; + const getIsInitCalled = () => !!getValueFromWindow(initFlagKey)?.isInitCalled; + /** + * Initializes the Visual Embed SDK globally and perform + * authentication if applicable. This function needs to be called before any ThoughtSpot + * component like Liveboard etc can be embedded. But need not wait for AuthEvent.SUCCESS + * to actually embed. That is handled internally. + * @param embedConfig The configuration object containing ThoughtSpot host, + * authentication mechanism and so on. + * @example + * ```js + * const authStatus = init({ + * thoughtSpotHost: 'https://my.thoughtspot.cloud', + * authType: AuthType.None, + * }); + * authStatus.on(AuthStatus.FAILURE, (reason) => { // do something here }); + * ``` + * @returns {@link AuthEventEmitter} event emitter which emits events on authentication success, + * failure and logout. See {@link AuthStatus} + * @version SDK: 1.0.0 | ThoughtSpot ts7.april.cl, 7.2.1 + * @group Authentication / Init + */ + const init = (embedConfig) => { + if (isWindowUndefined()) + return null; + sanity(embedConfig); + resetAllCachedServices(); + embedConfig = setEmbedConfig(backwardCompat({ + ...CONFIG_DEFAULTS, + ...embedConfig, + thoughtSpotHost: getThoughtSpotHost(embedConfig), + })); + setGlobalLogLevelOverride(embedConfig.logLevel); + registerReportingObserver(); + const authEE = new eventemitter3(); + setAuthEE(authEE); + handleAuth(); + const { password, ...configToTrack } = getEmbedConfig(); + uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_CALLED_INIT, { + ...configToTrack, + usedCustomizationSheet: embedConfig.customizations?.style?.customCSSUrl != null, + usedCustomizationVariables: embedConfig.customizations?.style?.customCSS?.variables != null, + usedCustomizationRules: embedConfig.customizations?.style?.customCSS?.rules_UNSTABLE != null, + usedCustomizationStrings: !!embedConfig.customizations?.content?.strings, + usedCustomizationIconSprite: !!embedConfig.customizations?.iconSpriteUrl, + }); + if (getEmbedConfig().callPrefetch) { + prefetch(getEmbedConfig().thoughtSpotHost); + } + // Resolves the promise created in the initPromiseKey + getValueFromWindow(initFlagKey).initPromiseResolve(authEE); + getValueFromWindow(initFlagKey).isInitCalled = true; + return authEE; + }; + /** + * + */ + function disableAutoLogin() { + getEmbedConfig().autoLogin = false; + } + let renderQueue = Promise.resolve(); + /** + * Renders functions in a queue, resolves to next function only after the callback next + * is called + * @param fn The function being registered + */ + const renderInQueue = (fn) => { + const { queueMultiRenders = false } = getEmbedConfig(); + if (queueMultiRenders) { + renderQueue = renderQueue.then(() => new Promise((res) => fn(res))); + return renderQueue; + } + // Sending an empty function to keep it consistent with the above usage. + return fn(() => { }); + }; + + /** + * Process the ExitPresentMode event and handle default fullscreen exit + * @param e - The event data + */ + function processExitPresentMode(e) { + const embedConfig = getEmbedConfig(); + const disableFullscreenPresentation = embedConfig?.disableFullscreenPresentation ?? true; + if (!disableFullscreenPresentation) { + handleExitPresentMode(); + } + } + /** + * Clears the cached preauth and session info. + */ + function processClearInfoCache() { + resetCachedPreauthInfo(); + resetCachedSessionInfo(); + } + /** + * + * @param e + * @param thoughtSpotHost + */ + function processCustomAction(e, thoughtSpotHost) { + const { session, embedAnswerData, contextMenuPoints } = e.data; + const answerService = new AnswerService(session, embedAnswerData || {}, thoughtSpotHost, contextMenuPoints?.selectedPoints); + return { + ...e, + answerService, + }; + } + /** + * Responds to AuthInit sent from host signifying successful authentication in host. + * @param e + * @returns {any} + */ + function processAuthInit(e) { + notifyAuthSuccess(); + // Expose only allowed details (eg: userGUID) back to SDK users. + return { + ...e, + data: { + userGUID: e.data?.userGUID || e.payload?.userGUID, + }, + }; + } + /** + * + * @param e + * @param containerEl + */ + function processNoCookieAccess(e, containerEl) { + const { loginFailedMessage, suppressNoCookieAccessAlert, ignoreNoCookieAccess, suppressErrorAlerts, } = getEmbedConfig(); + if (!ignoreNoCookieAccess) { + if (!suppressNoCookieAccessAlert && !suppressErrorAlerts) { + alert(ERROR_MESSAGE.THIRD_PARTY_COOKIE_BLOCKED_ALERT); + } + containerEl.innerHTML = loginFailedMessage; + } + notifyAuthFailure(AuthFailureType.NO_COOKIE_ACCESS); + return e; + } + /** + * + * @param e + * @param containerEl + */ + function processAuthFailure(e, containerEl) { + const { loginFailedMessage, authType, disableLoginFailurePage, autoLogin, } = getEmbedConfig(); + const isEmbeddedSSO = authType === AuthType.EmbeddedSSO; + const isTrustedAuth = authType === AuthType.TrustedAuthToken || authType === AuthType.TrustedAuthTokenCookieless; + const isEmbeddedSSOInfoFailure = isEmbeddedSSO && e?.data?.type === AuthFailureType.UNAUTHENTICATED_FAILURE; + if (autoLogin && isTrustedAuth) { + containerEl.innerHTML = loginFailedMessage; + notifyAuthFailure(AuthFailureType.IDLE_SESSION_TIMEOUT); + } + else if (authType !== AuthType.None && !disableLoginFailurePage && !isEmbeddedSSOInfoFailure) { + containerEl.innerHTML = loginFailedMessage; + notifyAuthFailure(AuthFailureType.OTHER); + } + resetCachedAuthToken(); + return e; + } + /** + * + * @param e + * @param containerEl + */ + function processAuthLogout(e, containerEl) { + const { loginFailedMessage } = getEmbedConfig(); + containerEl.innerHTML = loginFailedMessage; + resetCachedAuthToken(); + disableAutoLogin(); + notifyLogout(); + return e; + } + /** + * + * @param type + * @param e + * @param thoughtSpotHost + * @param containerEl + */ + function processEventData(type, eventData, thoughtSpotHost, containerEl) { + switch (type) { + case exports.EmbedEvent.CustomAction: + return processCustomAction(eventData, thoughtSpotHost); + case exports.EmbedEvent.AuthInit: + return processAuthInit(eventData); + case exports.EmbedEvent.NoCookieAccess: + return processNoCookieAccess(eventData, containerEl); + case exports.EmbedEvent.AuthFailure: + return processAuthFailure(eventData, containerEl); + case exports.EmbedEvent.AuthLogout: + return processAuthLogout(eventData, containerEl); + case exports.EmbedEvent.ExitPresentMode: + return processExitPresentMode(); + case exports.EmbedEvent.CLEAR_INFO_CACHE: + return processClearInfoCache(); + } + return eventData; + } + + function isValidUpdateFiltersPayload(payload) { + if (!payload) + return false; + const isValidFilter = (f) => { + const hasColumn = typeof f.column === 'string' || typeof f.columnName === 'string'; + const hasOperator = typeof f.oper === 'string' || typeof f.operator === 'string'; + const hasValues = Array.isArray(f.values); + const validType = !f.type || typeof f.type === 'string'; + return hasColumn && hasOperator && hasValues && validType; + }; + const hasValidFilter = payload.filter && isValidFilter(payload.filter); + const hasValidFilters = Array.isArray(payload.filters) && payload.filters.length > 0 && payload.filters.every(isValidFilter); + return !!(hasValidFilter || hasValidFilters); + } + function isValidDrillDownPayload(payload) { + if (!payload) + return false; + const points = payload.points; + if (!points || typeof points !== 'object') + return false; + const hasClickedPoint = 'clickedPoint' in points && points.clickedPoint != null; + const hasSelectedPoints = Array.isArray(points.selectedPoints) && points.selectedPoints.length > 0; + return hasClickedPoint || hasSelectedPoints; + } + function createValidationError(message) { + const err = new Error(message); + err.isValidationError = true; + err.embedErrorDetails = { + type: exports.EmbedEvent.Error, + data: { + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message, + code: EmbedErrorCodes.HOST_EVENT_VALIDATION, + error: message + }, + status: embedEventStatus.END + }; + throw err; + } + function throwUpdateFiltersValidationError() { + createValidationError(ERROR_MESSAGE.UPDATEFILTERS_INVALID_PAYLOAD); + } + function throwDrillDownValidationError() { + createValidationError(ERROR_MESSAGE.DRILLDOWN_INVALID_PAYLOAD); + } + + /** + * Maps HostEvent to its corresponding UIPassthroughEvent. + * Includes both custom-handler events (Pin, SaveAnswer, UpdateFilters, DrillDown) + * and getter events (GetAnswerSession, GetFilters, etc.) that use getDataWithPassthroughFallback. + */ + const PASSTHROUGH_MAP = { + // Custom handlers (setters with special logic) + [exports.HostEvent.Pin]: UIPassthroughEvent.PinAnswerToLiveboard, + [exports.HostEvent.SaveAnswer]: UIPassthroughEvent.SaveAnswer, + [exports.HostEvent.UpdateFilters]: UIPassthroughEvent.UpdateFilters, + [exports.HostEvent.DrillDown]: UIPassthroughEvent.Drilldown, + // Getters (use getDataWithPassthroughFallback) + [exports.HostEvent.GetAnswerSession]: UIPassthroughEvent.GetAnswerSession, + [exports.HostEvent.GetFilters]: UIPassthroughEvent.GetFilters, + [exports.HostEvent.GetIframeUrl]: UIPassthroughEvent.GetIframeUrl, + [exports.HostEvent.GetParameters]: UIPassthroughEvent.GetParameters, + [exports.HostEvent.GetTML]: UIPassthroughEvent.GetTML, + [exports.HostEvent.GetTabs]: UIPassthroughEvent.GetTabs, + [exports.HostEvent.getExportRequestForCurrentPinboard]: UIPassthroughEvent.GetExportRequestForCurrentPinboard, + }; + class HostEventClient { + constructor(iFrame) { + /** Cached list of available UI passthrough keys from the embedded app */ + this.availablePassthroughKeysCache = null; + this.iFrame = iFrame; + this.customHandlers = { + [exports.HostEvent.Pin]: (p, c) => this.handlePinEvent(p, c), + [exports.HostEvent.SaveAnswer]: (p, c) => this.handleSaveAnswerEvent(p, c), + [exports.HostEvent.UpdateFilters]: (p, c) => this.handleUpdateFiltersEvent(p, c), + [exports.HostEvent.DrillDown]: (p, c) => this.handleDrillDownEvent(p, c), + }; + } + /** + * A wrapper over process trigger to + * @param {HostEvent} message Host event to send + * @param {any} data Data to send with the host event + * @returns {Promise} - the response from the process trigger + */ + async processTrigger(message, data, context) { + if (!this.iFrame) { + throw new Error('Iframe element is not set'); + } + const thoughtspotHost = getEmbedConfig().thoughtSpotHost; + return processTrigger(this.iFrame, message, thoughtspotHost, data, context); + } + async handleHostEventWithParam(apiName, parameters, context) { + const response = (await this.triggerUIPassthroughApi(apiName, parameters, context)) + ?.find?.((r) => r.error || r.value); + if (!response) { + const error = `No answer found${parameters.vizId ? ` for vizId: ${parameters.vizId}` : ''}.`; + throw { error }; + } + const errors = response.error + || response.value?.errors + || response.value?.error; + if (errors) { + const message = typeof errors === 'string' ? errors : JSON.stringify(errors); + throw { error: message }; + } + return { ...response.value }; + } + async hostEventFallback(hostEvent, data, context) { + return this.processTrigger(hostEvent, data, context); + } + /** + * For getter events that return data. Tries UI passthrough first; + * if the app doesn't support it (no response data), falls back to + * the legacy host event channel. Real errors are thrown as-is. + */ + async getDataWithPassthroughFallback(passthroughEvent, hostEvent, payload, context) { + const response = await this.triggerUIPassthroughApi(passthroughEvent, payload || {}, context); + const matched = response?.find?.((r) => r.error || r.value); + if (!matched) { + return this.hostEventFallback(hostEvent, payload, context); + } + const errors = matched.error + || matched.value?.errors + || matched.value?.error; + if (errors) { + const message = typeof errors === 'string' ? errors : JSON.stringify(errors); + throw new Error(message); + } + return { ...matched.value }; + } + /** + * Setter for the iframe element used for host events + * @param {HTMLIFrameElement} iFrame - the iframe element to set + */ + setIframeElement(iFrame) { + this.iFrame = iFrame; + } + /** + * Fetches the list of available UI passthrough keys from the embedded app. + * Result is cached for the session. Returns empty array on failure. + */ + async getAvailableUIPassthroughKeys(context) { + if (this.availablePassthroughKeysCache !== null) { + return this.availablePassthroughKeysCache; + } + try { + const response = await this.triggerUIPassthroughApi(UIPassthroughEvent.GetAvailableUIPassthroughs, {}, context); + const matched = response?.find?.((r) => r.value && !r.error); + const keys = matched?.value?.keys; + this.availablePassthroughKeysCache = Array.isArray(keys) ? keys : []; + return this.availablePassthroughKeysCache; + } + catch { + return []; + } + } + async triggerUIPassthroughApi(apiName, parameters, context) { + const res = await this.processTrigger(exports.HostEvent.UIPassthrough, { + type: apiName, + parameters, + }, context); + return res; + } + async handlePinEvent(payload, context) { + if (!payload || !('newVizName' in payload)) { + return this.hostEventFallback(exports.HostEvent.Pin, payload, context); + } + const formattedPayload = { + ...payload, + pinboardId: payload.liveboardId ?? payload.pinboardId, + newPinboardName: payload.newLiveboardName ?? payload.newPinboardName, + }; + const data = await this.handleHostEventWithParam(UIPassthroughEvent.PinAnswerToLiveboard, formattedPayload, context); + return { + ...data, + liveboardId: data.pinboardId, + }; + } + async handleSaveAnswerEvent(payload, context) { + if (!payload || !('name' in payload) || !('description' in payload)) { + // Save is the fallback for SaveAnswer + return this.hostEventFallback(exports.HostEvent.Save, payload, context); + } + const data = await this.handleHostEventWithParam(UIPassthroughEvent.SaveAnswer, payload, context); + return { + ...data, + answerId: data?.saveResponse?.data?.Answer__save?.answer?.id, + }; + } + handleUpdateFiltersEvent(payload, context) { + if (!isValidUpdateFiltersPayload(payload)) { + throwUpdateFiltersValidationError(); + } + return this.handleHostEventWithParam(UIPassthroughEvent.UpdateFilters, payload, context); + } + handleDrillDownEvent(payload, context) { + if (!isValidDrillDownPayload(payload)) { + throwDrillDownValidationError(); + } + return this.handleHostEventWithParam(UIPassthroughEvent.Drilldown, payload, context); + } + /** + * Dispatches a host event using the appropriate channel: + * 1. If the embedded app supports UI passthrough for this event, use it (custom handler or getter). + * 2. Otherwise fall back to the legacy host event channel. + * + * @param hostEvent - The host event to trigger + * @param payload - Optional payload for the event + * @param context - Optional context (e.g. vizId) for scoped operations + */ + async triggerHostEvent(hostEvent, payload, context) { + const customHandler = this.customHandlers[hostEvent]; + const passthroughEvent = PASSTHROUGH_MAP[hostEvent]; + // If embedded app supports passthrough but not this event, use legacy channel + const keys = passthroughEvent ? await this.getAvailableUIPassthroughKeys(context) : []; + if (passthroughEvent && keys.length > 0 && !keys.includes(passthroughEvent)) { + return this.hostEventFallback(hostEvent, payload, context); + } + // Custom handler (setters) > getter passthrough > legacy fallback + return (customHandler + ? customHandler(payload, context) + : passthroughEvent + ? this.getDataWithPassthroughFallback(passthroughEvent, hostEvent, payload, context) + : this.hostEventFallback(hostEvent, payload, context)); + } + } + + const DefaultInterceptUrlsMap = { + [InterceptedApiType.AnswerData]: [ + '/prism/?op=GetChartWithData', + '/prism/?op=GetTableWithHeadlineData', + '/prism/?op=GetTableWithData', + ], + [InterceptedApiType.LiveboardData]: [ + '/prism/?op=LoadContextBook' + ], + }; + const formatInterceptUrl = (url) => { + const host = getThoughtSpotHost(getEmbedConfig()); + if (url.startsWith('/')) + return `${host}${url}`; + return url; + }; + /** + * Converts user passed url values to proper urls + * [ANSER_DATA] => ['https://host/pris/op?=op'] + * @param interceptUrls + * @returns + */ + const processInterceptUrls = (interceptUrls) => { + let processedUrls = [...interceptUrls]; + Object.entries(DefaultInterceptUrlsMap).forEach(([apiType, apiTypeUrls]) => { + if (!processedUrls.includes(apiType)) + return; + processedUrls = processedUrls.filter(url => url !== apiType); + processedUrls = [...processedUrls, ...apiTypeUrls]; + }); + return processedUrls.map(url => formatInterceptUrl(url)); + }; + /** + * Returns the data to be sent to embed to setup intercepts + * the urls to intercept, timeout etc + * @param viewConfig + * @returns + */ + const getInterceptInitData = (viewConfig) => { + const combinedUrls = [...(viewConfig.interceptUrls || [])]; + if (viewConfig.isOnBeforeGetVizDataInterceptEnabled) { + combinedUrls.push(InterceptedApiType.AnswerData); + } + const shouldInterceptAll = combinedUrls.includes(InterceptedApiType.ALL); + const interceptUrls = shouldInterceptAll ? [InterceptedApiType.ALL] : processInterceptUrls(combinedUrls); + const interceptTimeout = viewConfig.interceptTimeout; + return { + interceptUrls, + interceptTimeout, + }; + }; + const parseJson = (jsonString) => { + try { + const json = JSON.parse(jsonString); + return [json, null]; + } + catch (error) { + return [null, error]; + } + }; + /** + * Parse the api intercept data and return the parsed data and error if any + * Embed returns the input and init from the fetch call + */ + const parseInterceptData = (eventDataString) => { + try { + const [parsedData, error] = parseJson(eventDataString); + if (error) { + return [null, error]; + } + const { input, init } = parsedData; + const [parsedBody, bodyParseError] = parseJson(init.body); + if (!bodyParseError) { + init.body = parsedBody; + } + const parsedInit = { input, init }; + return [parsedInit, null]; + } + catch (error) { + return [null, error]; + } + }; + const getUrlType = (url) => { + for (const [apiType, apiTypeUrls] of Object.entries(DefaultInterceptUrlsMap)) { + if (apiTypeUrls.includes(url)) + return apiType; + } + // TODO: have a unknown type maybe ?? + return InterceptedApiType.ALL; + }; + /** + * Handle Api intercept event and simulate legacy onBeforeGetVizDataIntercept event + * + * embed sends -> ApiIntercept -> we send + * ApiIntercept + * OnBeforeGetVizDataIntercept (if url is part of DefaultUrlMap.AnswerData) + * + * @param params + * @returns + */ + const handleInterceptEvent = async (params) => { + const { eventData, executeEvent, viewConfig, getUnsavedAnswerTml } = params; + const [interceptData, bodyParseError] = parseInterceptData(eventData.data); + if (bodyParseError) { + const errorDetails = { + errorType: ErrorDetailsTypes.API, + message: ERROR_MESSAGE.ERROR_PARSING_API_INTERCEPT_BODY, + code: EmbedErrorCodes.PARSING_API_INTERCEPT_BODY_ERROR, + error: ERROR_MESSAGE.ERROR_PARSING_API_INTERCEPT_BODY, + }; + executeEvent(exports.EmbedEvent.Error, errorDetails); + logger$3.error('Error parsing request body', bodyParseError); + return; + } + const { input: requestUrl, init } = interceptData; + const sessionId = init?.body?.variables?.session?.sessionId; + const vizId = init?.body?.variables?.contextBookId; + const answerDataUrls = DefaultInterceptUrlsMap[InterceptedApiType.AnswerData]; + const legacyInterceptEnabled = viewConfig.isOnBeforeGetVizDataInterceptEnabled; + const isAnswerDataUrl = answerDataUrls.includes(requestUrl); + const sendLegacyIntercept = isAnswerDataUrl && legacyInterceptEnabled; + if (sendLegacyIntercept) { + const answerTml = await getUnsavedAnswerTml({ sessionId, vizId }); + // Build the legacy payload for backwards compatibility + const legacyPayload = { + data: { + data: answerTml, + status: embedEventStatus.END, + type: exports.EmbedEvent.OnBeforeGetVizDataIntercept + } + }; + executeEvent(exports.EmbedEvent.OnBeforeGetVizDataIntercept, legacyPayload); + } + const urlType = getUrlType(requestUrl); + executeEvent(exports.EmbedEvent.ApiIntercept, { ...interceptData, urlType }); + }; + /** + * Support both the legacy and new format of the api intercept response + * @param payload + * @returns + */ + const processApiInterceptResponse = (payload) => { + const isLegacyFormat = payload?.data?.error; + if (isLegacyFormat) { + return processLegacyInterceptResponse(payload); + } + return payload; + }; + const processLegacyInterceptResponse = (payload) => { + const errorText = payload?.data?.error?.errorText; + const errorDescription = payload?.data?.error?.errorDescription; + const payloadToSend = { + execute: payload?.data?.execute, + response: { + body: { + errors: [ + { + title: errorText, + description: errorDescription, + isUserError: true, + }, + ], + data: {}, + }, + }, + }; + return { data: payloadToSend }; + }; + + /** + * Copyright (c) 2022 + * + * Base classes + * @summary Base classes + * @author Ayon Ghosh + */ + /** + * Global prefix for all ThoughtSpot postHash Params. + */ + const THOUGHTSPOT_PARAM_PREFIX = 'ts-'; + const TS_EMBED_ID = '_thoughtspot-embed'; + const VERSION = packageInfo.version; + /** + * The event id map from v2 event names to v1 event id + * v1 events are the classic embed events implemented in Blink v1 + * We cannot rename v1 event types to maintain backward compatibility + * @internal + */ + const V1EventMap = {}; + /** + * Base class for embedding v2 experience + * Note: the v2 version of ThoughtSpot Blink is built on the new stack: + * React+GraphQL + */ + class TsEmbed { + /** + * Setter for the iframe element + * @param {HTMLIFrameElement} iFrame HTMLIFrameElement + */ + setIframeElement(iFrame) { + this.iFrame = iFrame; + this.hostEventClient.setIframeElement(iFrame); + } + constructor(domSelector, viewConfig) { + /** + * The key to store the embed instance in the DOM node + */ + this.embedNodeKey = '__tsEmbed'; + this.isAppInitialized = false; + /** + * Should we encode URL Query Params using base64 encoding which ThoughtSpot + * will generate for embedding. This provides additional security to + * ThoughtSpot clusters against Cross site scripting attacks. + * @default false + */ + this.shouldEncodeUrlQueryParams = false; + this.defaultHiddenActions = [exports.Action.ReportError]; + /** + * Handler for fullscreen change events + */ + this.fullscreenChangeHandler = null; + this.subscribedListeners = {}; + this.messageEventListener = (event) => { + const eventType = this.getEventType(event); + const eventPort = this.getEventPort(event); + const eventData = this.formatEventData(event, eventType); + if (event.source === this.iFrame.contentWindow) { + const processedEventData = processEventData(eventType, eventData, this.thoughtSpotHost, this.isPreRendered ? this.preRenderWrapper : this.hostElement); + if (eventType === exports.EmbedEvent.ApiIntercept) { + this.handleApiInterceptEvent({ eventData, eventPort }); + return; + } + this.executeCallbacks(eventType, processedEventData, eventPort); + } + }; + /** + * Send Custom style as part of payload of APP_INIT + * @param _ + * @param responder + */ + this.appInitCb = async (_, responder) => { + try { + const appInitData = await this.getAppInitData(); + this.isAppInitialized = true; + responder({ + type: exports.EmbedEvent.APP_INIT, + data: appInitData, + }); + } + catch (e) { + logger$3.error(`AppInit failed, Error : ${e?.message}`); + } + }; + this.handleAuthFailure = (error) => { + logger$3.error(`${ERROR_MESSAGE.INVALID_TOKEN_ERROR} Error : ${error?.message}`); + processAuthFailure(error, this.isPreRendered ? this.preRenderWrapper : this.hostElement); + }; + /** + * Refresh the auth token if the autoLogin is true and the authType is TrustedAuthTokenCookieless + * @param _ + * @param responder + */ + this.tokenRefresh = async (_, responder) => { + try { + await this.refreshAuthTokenForCookieless(responder, exports.EmbedEvent.RefreshAuthToken, true); + } + catch (e) { + this.handleAuthFailure(e); + } + }; + /** + * Sends updated auth token to the iFrame to avoid user logout + * @param _ + * @param responder + */ + this.updateAuthToken = async (_, responder) => { + const { authType, autoLogin: autoLoginConfig } = this.embedConfig; + // Default autoLogin: true for cookieless if undefined/null, otherwise + // false + const autoLogin = autoLoginConfig ?? (authType === AuthType.TrustedAuthTokenCookieless); + try { + await this.refreshAuthTokenForCookieless(responder, exports.EmbedEvent.AuthExpire, false); + } + catch (e) { + this.handleAuthFailure(e); + } + if (autoLogin && authType !== AuthType.TrustedAuthTokenCookieless) { + handleAuth(); + } + notifyAuthFailure(AuthFailureType.EXPIRY); + }; + /** + * Auto Login and send updated authToken to the iFrame to avoid user session logout + * @param _ + * @param responder + */ + this.idleSessionTimeout = (_, responder) => { + handleAuth().then(async () => { + let authToken = ''; + try { + authToken = await getAuthenticationToken(this.embedConfig); + responder({ + type: exports.EmbedEvent.IdleSessionTimeout, + data: { authToken }, + }); + } + catch (e) { + this.handleAuthFailure(e); + } + }).catch((e) => { + logger$3.error(`Auto Login failed, Error : ${e?.message}`); + }); + notifyAuthFailure(AuthFailureType.IDLE_SESSION_TIMEOUT); + }; + /** + * Register APP_INIT event and sendback init payload + */ + this.registerAppInit = () => { + this.on(exports.EmbedEvent.APP_INIT, this.appInitCb, { start: false }, true); + this.on(exports.EmbedEvent.AuthExpire, this.updateAuthToken, { start: false }, true); + this.on(exports.EmbedEvent.IdleSessionTimeout, this.idleSessionTimeout, { start: false }, true); + const embedListenerReadyHandler = this.createEmbedContainerHandler(exports.EmbedEvent.EmbedListenerReady); + this.on(exports.EmbedEvent.EmbedListenerReady, embedListenerReadyHandler, { start: false }, true); + const authInitHandler = this.createEmbedContainerHandler(exports.EmbedEvent.AuthInit); + this.on(exports.EmbedEvent.AuthInit, authInitHandler, { start: false }, true); + this.on(exports.EmbedEvent.RefreshAuthToken, this.tokenRefresh, { start: false }, true); + }; + this.showPreRenderByDefault = false; + /** + * We can process the customer given payload before sending it to the embed port + * Embed event handler -> responder -> createEmbedEventResponder -> send response + * @param eventPort The event port for a specific MessageChannel + * @param eventType The event type + * @returns + */ + this.createEmbedEventResponder = (eventPort, eventType) => { + const getPayloadToSend = (payload) => { + if (eventType === exports.EmbedEvent.OnBeforeGetVizDataIntercept) { + return processLegacyInterceptResponse(payload); + } + if (eventType === exports.EmbedEvent.ApiIntercept) { + return processApiInterceptResponse(payload); + } + return payload; + }; + return (payload) => { + const payloadToSend = getPayloadToSend(payload); + this.triggerEventOnPort(eventPort, payloadToSend); + }; + }; + /** + * @hidden + * Internal state to track if the embed container is loaded. + * This is used to trigger events after the embed container is loaded. + */ + this.isEmbedContainerLoaded = false; + /** + * @hidden + * Internal state to track the callbacks to be executed after the embed container + * is loaded. + * This is used to trigger events after the embed container is loaded. + */ + this.embedContainerReadyCallbacks = []; + this.createEmbedContainerHandler = (source) => () => { + const processEmbedContainerReady = () => { + logger$3.debug('processEmbedContainerReady'); + this.isEmbedContainerLoaded = true; + this.executeEmbedContainerReadyCallbacks(); + }; + if (source === exports.EmbedEvent.AuthInit) { + const AUTH_INIT_FALLBACK_DELAY = 1000; + // Wait for 1 second to ensure the embed container is loaded + // This is a workaround to ensure the embed container is loaded + // this is needed until all clusters have EmbedListenerReady event + setTimeout(processEmbedContainerReady, AUTH_INIT_FALLBACK_DELAY); + } + else if (source === exports.EmbedEvent.EmbedListenerReady) { + processEmbedContainerReady(); + } + }; + this.hostElement = getDOMNode(domSelector); + this.eventHandlerMap = new Map(); + this.isError = false; + this.viewConfig = { + excludeRuntimeFiltersfromURL: true, + excludeRuntimeParametersfromURL: true, + ...viewConfig, + }; + this.registerAppInit(); + uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_EMBED_CREATE, { + ...viewConfig, + sdkVersion: VERSION, + }); + const embedConfig = getEmbedConfig(); + if (embedConfig) { + this.embedConfig = embedConfig; + this.thoughtSpotHost = getThoughtSpotHost(embedConfig); + this.thoughtSpotV2Base = getV2BasePath(embedConfig); + } + this.hostEventClient = new HostEventClient(this.iFrame); + this.shouldWaitForRenderPromise = !getIsInitCompleted(); + const afterInit = () => { + this.embedConfig = embedConfig; + if (!embedConfig.authTriggerContainer && !embedConfig.useEventForSAMLPopup) { + this.embedConfig.authTriggerContainer = domSelector; + } + this.thoughtSpotHost = getThoughtSpotHost(embedConfig); + this.thoughtSpotV2Base = getV2BasePath(embedConfig); + this.shouldEncodeUrlQueryParams = embedConfig.shouldEncodeUrlQueryParams; + }; + if (!this.shouldWaitForRenderPromise) { + afterInit(); + } + else { + this.isReadyForRenderPromise = getInitPromise().then(afterInit).finally(() => { + this.shouldWaitForRenderPromise = true; + }); + } + } + /** + * Throws error encountered during initialization. + */ + throwInitError() { + this.handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.INIT_SDK_REQUIRED, + code: EmbedErrorCodes.INIT_ERROR, + error: ERROR_MESSAGE.INIT_SDK_REQUIRED, + }); + } + /** + * Handles errors within the SDK + * @param error The error message or object + * @param errorDetails The error details + */ + handleError(errorDetails) { + this.isError = true; + this.executeCallbacks(exports.EmbedEvent.Error, errorDetails); + // Log error + logger$3.error(errorDetails); + } + /** + * Extracts the type field from the event payload + * @param event The window message event + */ + getEventType(event) { + return event.data?.type || event.data?.__type; + } + /** + * Extracts the port field from the event payload + * @param event The window message event + * @returns + */ + getEventPort(event) { + if (event.ports.length && event.ports[0]) { + return event.ports[0]; + } + return null; + } + /** + * Checks if preauth cache is enabled + * from the view config and embed config + * @returns boolean + */ + isPreAuthCacheEnabled() { + // Disable preauth cache when: + // 1. overrideOrgId is present since: + // - cached auth info would be for wrong org + // - info call response changes for each different overrideOrgId + // 2. disablePreauthCache is explicitly set to true + // 3. FullAppEmbed has primary navbar visible since: + // - primary navbar requires fresh auth state for navigation + // - cached auth may not reflect current user permissions + const isDisabled = (this.viewConfig.overrideOrgId !== undefined + || this.embedConfig.disablePreauthCache === true + || this.isFullAppEmbedWithVisiblePrimaryNavbar()); + return !isDisabled; + } + /** + * Checks if current embed is FullAppEmbed with visible primary navbar + * @returns boolean + */ + isFullAppEmbedWithVisiblePrimaryNavbar() { + const appViewConfig = this.viewConfig; + // Check if this is a FullAppEmbed (AppEmbed) + // showPrimaryNavbar defaults to true if not explicitly set to false + return (appViewConfig.embedComponentType === 'AppEmbed' + && appViewConfig.showPrimaryNavbar === true); + } + /** + * fix for ts7.sep.cl + * will be removed for ts7.oct.cl + * @param event + * @param eventType + * @hidden + */ + formatEventData(event, eventType) { + const eventData = { + ...event.data, + type: eventType, + }; + if (!eventData.data) { + eventData.data = event.data.payload; + } + return eventData; + } + /** + * Subscribe to network events (online/offline) that should + * work regardless of auth status + */ + subscribeToNetworkEvents() { + this.unsubscribeToNetworkEvents(); + const onlineEventListener = (e) => { + this.trigger(exports.HostEvent.Reload); + }; + window.addEventListener('online', onlineEventListener); + const offlineEventListener = (e) => { + const errorDetails = { + errorType: ErrorDetailsTypes.NETWORK, + message: ERROR_MESSAGE.OFFLINE_WARNING, + code: EmbedErrorCodes.NETWORK_ERROR, + offlineWarning: ERROR_MESSAGE.OFFLINE_WARNING, + }; + this.executeCallbacks(exports.EmbedEvent.Error, errorDetails); + logger$3.warn(errorDetails); + }; + window.addEventListener('offline', offlineEventListener); + this.subscribedListeners.online = onlineEventListener; + this.subscribedListeners.offline = offlineEventListener; + } + handleApiInterceptEvent({ eventData, eventPort }) { + const executeEvent = (_eventType, data) => { + this.executeCallbacks(_eventType, data, eventPort); + }; + const getUnsavedAnswerTml = async (props) => { + const response = await this.triggerUIPassThrough(UIPassthroughEvent.GetUnsavedAnswerTML, props); + return response.filter((item) => item.value)?.[0]?.value; + }; + handleInterceptEvent({ eventData, executeEvent, viewConfig: this.viewConfig, getUnsavedAnswerTml }); + } + /** + * Subscribe to message events that depend on successful iframe setup + */ + subscribeToMessageEvents() { + this.unsubscribeToMessageEvents(); + window.addEventListener('message', this.messageEventListener); + this.subscribedListeners.message = this.messageEventListener; + } + /** + * Adds event listeners for both network and message events. + * This maintains backward compatibility with the existing method. + * Adds a global event listener to window for "message" events. + * ThoughtSpot detects if a particular event is targeted to this + * embed instance through an identifier contained in the payload, + * and executes the registered callbacks accordingly. + */ + subscribeToEvents() { + this.subscribeToNetworkEvents(); + this.subscribeToMessageEvents(); + } + unsubscribeToNetworkEvents() { + if (this.subscribedListeners.online) { + window.removeEventListener('online', this.subscribedListeners.online); + delete this.subscribedListeners.online; + } + if (this.subscribedListeners.offline) { + window.removeEventListener('offline', this.subscribedListeners.offline); + delete this.subscribedListeners.offline; + } + } + unsubscribeToMessageEvents() { + if (this.subscribedListeners.message) { + window.removeEventListener('message', this.subscribedListeners.message); + delete this.subscribedListeners.message; + } + } + unsubscribeToEvents() { + Object.keys(this.subscribedListeners).forEach((key) => { + window.removeEventListener(key, this.subscribedListeners[key]); + }); + } + async getAuthTokenForCookielessInit() { + let authToken = ''; + if (this.embedConfig.authType !== AuthType.TrustedAuthTokenCookieless) + return authToken; + try { + authToken = await getAuthenticationToken(this.embedConfig); + } + catch (e) { + processAuthFailure(e, this.isPreRendered ? this.preRenderWrapper : this.hostElement); + throw e; + } + return authToken; + } + async getDefaultAppInitData() { + const authToken = await this.getAuthTokenForCookielessInit(); + const customActionsResult = getCustomActions([ + ...(this.viewConfig.customActions || []), + ...(this.embedConfig.customActions || []) + ]); + if (customActionsResult.errors.length > 0) { + this.handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: customActionsResult.errors, + code: EmbedErrorCodes.CUSTOM_ACTION_VALIDATION, + error: { type: EmbedErrorCodes.CUSTOM_ACTION_VALIDATION, message: customActionsResult.errors } + }); + } + const baseInitData = { + customisations: getCustomisations(this.embedConfig, this.viewConfig), + authToken, + runtimeFilterParams: this.viewConfig.excludeRuntimeFiltersfromURL + ? getRuntimeFilters(this.viewConfig.runtimeFilters) + : null, + runtimeParameterParams: this.viewConfig.excludeRuntimeParametersfromURL + ? getRuntimeParameters(this.viewConfig.runtimeParameters || []) + : null, + hiddenHomepageModules: this.viewConfig.hiddenHomepageModules || [], + reorderedHomepageModules: this.viewConfig.reorderedHomepageModules || [], + hostConfig: this.embedConfig.hostConfig, + hiddenHomeLeftNavItems: this.viewConfig?.hiddenHomeLeftNavItems + ? this.viewConfig?.hiddenHomeLeftNavItems + : [], + customVariablesForThirdPartyTools: this.embedConfig.customVariablesForThirdPartyTools || {}, + hiddenListColumns: this.viewConfig.hiddenListColumns || [], + customActions: customActionsResult.actions, + embedExpiryInAuthToken: this.viewConfig.refreshAuthTokenOnNearExpiry ?? true, + ...getInterceptInitData(this.viewConfig), + ...getHostEventsConfig(this.viewConfig), + }; + return baseInitData; + } + async getAppInitData() { + return this.getDefaultAppInitData(); + } + /** + * Helper method to refresh/update auth token for TrustedAuthTokenCookieless auth type + * @param responder - Function to send response back + * @param eventType - The embed event type to send + * @param forceRefresh - Whether to force refresh the token + * @returns Promise that resolves if token was refreshed, rejects otherwise + */ + async refreshAuthTokenForCookieless(responder, eventType, forceRefresh = false) { + const { authType, autoLogin } = this.embedConfig; + const isAutoLoginTrue = autoLogin ?? (authType === AuthType.TrustedAuthTokenCookieless); + if (isAutoLoginTrue && authType === AuthType.TrustedAuthTokenCookieless) { + const authToken = await getAuthenticationToken(this.embedConfig, forceRefresh); + responder({ + type: eventType, + data: { authToken }, + }); + } + } + /** + * Constructs the base URL string to load the ThoughtSpot app. + * @param query + */ + getEmbedBasePath(query) { + let queryString = query.startsWith('?') ? query : `?${query}`; + if (this.shouldEncodeUrlQueryParams) { + queryString = `?base64UrlEncodedFlags=${getEncodedQueryParamsString(queryString.substr(1))}`; + } + const basePath = [this.thoughtSpotHost, this.thoughtSpotV2Base, queryString] + .filter((x) => x.length > 0) + .join('/'); + return `${basePath}#`; + } + async getUpdateEmbedParamsObject() { + let queryParams = this.getEmbedParamsObject(); + const appInitData = await this.getAppInitData(); + queryParams = { ...this.viewConfig, ...queryParams, ...appInitData }; + return queryParams; + } + /** + * Common query params set for all the embed modes. + * @param queryParams + * @returns queryParams + */ + getBaseQueryParams(queryParams = {}) { + let hostAppUrl = window?.location?.host || ''; + // The below check is needed because TS Cloud firewall, blocks + // localhost/127.0.0.1 in any url param. + if (hostAppUrl.includes('localhost') || hostAppUrl.includes('127.0.0.1')) { + hostAppUrl = 'local-host'; + } + const blockNonEmbedFullAppAccess = this.embedConfig.blockNonEmbedFullAppAccess ?? true; + queryParams[Param.EmbedApp] = true; + queryParams[Param.HostAppUrl] = encodeURIComponent(hostAppUrl); + queryParams[Param.ViewPortHeight] = window.innerHeight; + queryParams[Param.ViewPortWidth] = window.innerWidth; + queryParams[Param.Version] = VERSION; + queryParams[Param.AuthType] = this.embedConfig.authType; + queryParams[Param.blockNonEmbedFullAppAccess] = blockNonEmbedFullAppAccess; + queryParams[Param.AutoLogin] = this.embedConfig.autoLogin; + if (this.embedConfig.disableLoginRedirect === true || this.embedConfig.autoLogin === true) { + queryParams[Param.DisableLoginRedirect] = true; + } + if (this.embedConfig.authType === AuthType.EmbeddedSSO) { + queryParams[Param.ForceSAMLAutoRedirect] = true; + } + if (this.embedConfig.authType === AuthType.TrustedAuthTokenCookieless) { + queryParams[Param.cookieless] = true; + } + if (this.embedConfig.pendoTrackingKey) { + queryParams[Param.PendoTrackingKey] = this.embedConfig.pendoTrackingKey; + } + if (this.embedConfig.numberFormatLocale) { + queryParams[Param.NumberFormatLocale] = this.embedConfig.numberFormatLocale; + } + if (this.embedConfig.dateFormatLocale) { + queryParams[Param.DateFormatLocale] = this.embedConfig.dateFormatLocale; + } + if (this.embedConfig.currencyFormat) { + queryParams[Param.CurrencyFormat] = this.embedConfig.currencyFormat; + } + const { disabledActions, disabledActionReason, hiddenActions, visibleActions, hiddenTabs, visibleTabs, showAlerts, additionalFlags: additionalFlagsFromView, locale, customizations, contextMenuTrigger, linkOverride, enableLinkOverridesV2, insertInToSlide, disableRedirectionLinksInNewTab, overrideOrgId, exposeTranslationIDs, primaryAction, } = this.viewConfig; + const { additionalFlags: additionalFlagsFromInit } = this.embedConfig; + const additionalFlags = { + ...additionalFlagsFromInit, + ...additionalFlagsFromView, + }; + if (Array.isArray(visibleActions) && Array.isArray(hiddenActions)) { + this.handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.CONFLICTING_ACTIONS_CONFIG, + code: EmbedErrorCodes.CONFLICTING_ACTIONS_CONFIG, + error: ERROR_MESSAGE.CONFLICTING_ACTIONS_CONFIG, + }); + return queryParams; + } + if (Array.isArray(visibleTabs) && Array.isArray(hiddenTabs)) { + this.handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.CONFLICTING_TABS_CONFIG, + code: EmbedErrorCodes.CONFLICTING_TABS_CONFIG, + error: ERROR_MESSAGE.CONFLICTING_TABS_CONFIG, + }); + return queryParams; + } + if (primaryAction) { + queryParams[Param.PrimaryAction] = primaryAction; + } + if (disabledActions?.length) { + queryParams[Param.DisableActions] = disabledActions; + } + if (disabledActionReason) { + queryParams[Param.DisableActionReason] = disabledActionReason; + } + if (exposeTranslationIDs) { + queryParams[Param.ExposeTranslationIDs] = exposeTranslationIDs; + } + queryParams[Param.HideActions] = [...this.defaultHiddenActions, ...(hiddenActions ?? [])]; + if (Array.isArray(visibleActions)) { + queryParams[Param.VisibleActions] = visibleActions; + } + if (Array.isArray(hiddenTabs)) { + queryParams[Param.HiddenTabs] = hiddenTabs; + } + if (Array.isArray(visibleTabs)) { + queryParams[Param.VisibleTabs] = visibleTabs; + } + /** + * Default behavior for context menu will be left-click + * from version 9.2.0.cl the user have an option to override context + * menu click + */ + if (contextMenuTrigger === ContextMenuTriggerOptions.LEFT_CLICK) { + queryParams[Param.ContextMenuTrigger] = 'left'; + } + else if (contextMenuTrigger === ContextMenuTriggerOptions.RIGHT_CLICK) { + queryParams[Param.ContextMenuTrigger] = 'right'; + } + else if (contextMenuTrigger === ContextMenuTriggerOptions.BOTH_CLICKS) { + queryParams[Param.ContextMenuTrigger] = 'both'; + } + const embedCustomizations = this.embedConfig.customizations; + const spriteUrl = customizations?.iconSpriteUrl || embedCustomizations?.iconSpriteUrl; + if (spriteUrl) { + queryParams[Param.IconSpriteUrl] = spriteUrl.replace('https://', ''); + } + const stringIDsUrl = customizations?.content?.stringIDsUrl + || embedCustomizations?.content?.stringIDsUrl; + if (stringIDsUrl) { + queryParams[Param.StringIDsUrl] = stringIDsUrl; + } + if (showAlerts !== undefined) { + queryParams[Param.ShowAlerts] = showAlerts; + } + if (locale !== undefined) { + queryParams[Param.Locale] = locale; + } + // TODO: Once V2 is stable, send both flags when + // linkOverride is true (remove the else-if). + if (enableLinkOverridesV2) { + queryParams[Param.EnableLinkOverridesV2] = true; + queryParams[Param.LinkOverride] = true; + } + else if (linkOverride) { + queryParams[Param.LinkOverride] = linkOverride; + } + if (insertInToSlide) { + queryParams[Param.ShowInsertToSlide] = insertInToSlide; + } + if (disableRedirectionLinksInNewTab) { + queryParams[Param.DisableRedirectionLinksInNewTab] = disableRedirectionLinksInNewTab; + } + if (overrideOrgId !== undefined) { + queryParams[Param.OverrideOrgId] = overrideOrgId; + } + if (this.isPreAuthCacheEnabled()) { + queryParams[Param.preAuthCache] = true; + } + queryParams[Param.OverrideNativeConsole] = true; + queryParams[Param.ClientLogLevel] = this.embedConfig.logLevel; + if (isObject_1(additionalFlags) && !isEmpty_1(additionalFlags)) { + Object.assign(queryParams, additionalFlags); + } + // Do not add any flags below this, as we want additional flags to + // override other flags + return queryParams; + } + /** + * Constructs the base URL string to load v1 of the ThoughtSpot app. + * This is used for embedding Liveboards, visualizations, and full application. + * @param queryString The query string to append to the URL. + * @param isAppEmbed A Boolean parameter to specify if you are embedding + * the full application. + */ + getV1EmbedBasePath(queryString) { + const queryParams = this.shouldEncodeUrlQueryParams + ? `?base64UrlEncodedFlags=${getEncodedQueryParamsString(queryString)}` + : `?${queryString}`; + const host = this.thoughtSpotHost; + const path = `${host}/${queryParams}#`; + return path; + } + getEmbedParams() { + const queryParams = this.getEmbedParamsObject(); + return getQueryParamString(queryParams); + } + getEmbedParamsObject() { + const params = this.getBaseQueryParams(); + return params; + } + getRootIframeSrc() { + const query = this.getEmbedParams(); + return this.getEmbedBasePath(query); + } + createIframeEl(frameSrc) { + const iFrame = document.createElement('iframe'); + iFrame.src = frameSrc; + iFrame.id = TS_EMBED_ID; + iFrame.setAttribute('data-ts-iframe', 'true'); + // according to screenfull.js documentation + // allowFullscreen, webkitallowfullscreen and mozallowfullscreen must be + // true + iFrame.allowFullscreen = true; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + iFrame.webkitallowfullscreen = true; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + iFrame.mozallowfullscreen = true; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + iFrame.allow = 'clipboard-read; clipboard-write; fullscreen; local-network-access;'; + const frameParams = this.viewConfig.frameParams; + const { height: frameHeight, width: frameWidth, ...restParams } = frameParams || {}; + const width = getCssDimension(frameWidth || DEFAULT_EMBED_WIDTH); + const height = getCssDimension(frameHeight || DEFAULT_EMBED_HEIGHT); + setAttributes(iFrame, restParams); + iFrame.style.width = `${width}`; + iFrame.style.height = `${height}`; + // Set minimum height to the frame so that, + // scaling down on the fullheight doesn't make it too small. + iFrame.style.minHeight = `${height}`; + iFrame.style.border = '0'; + iFrame.name = 'ThoughtSpot Embedded Analytics'; + return iFrame; + } + /** + * Returns true if this embed instance is configured for pre-rendering. + */ + isPreRenderEmbed() { + return !!this.viewConfig.preRenderId; + } + handleInsertionIntoDOM(child) { + if (this.isPreRenderEmbed()) { + this.insertIntoDOMForPreRender(child); + } + else { + this.insertIntoDOM(child); + } + if (this.insertedDomEl instanceof Node) { + this.insertedDomEl[this.embedNodeKey] = this; + } + if (this.preRenderWrapper) { + this.preRenderWrapper[this.embedNodeKey] = this; + } + } + /** + * Renders the embedded ThoughtSpot app in an iframe and sets up + * event listeners. + * @param url - The URL of the embedded ThoughtSpot app. + */ + async renderIFrame(url) { + if (this.isError) { + return null; + } + if (!this.thoughtSpotHost) { + this.throwInitError(); + } + if (url.length > URL_MAX_LENGTH) ; + return renderInQueue((nextInQueue) => { + const initTimestamp = Date.now(); + this.executeCallbacks(exports.EmbedEvent.Init, { + data: { + timestamp: initTimestamp, + }, + type: exports.EmbedEvent.Init, + }); + uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_RENDER_START); + // Always subscribe to network events, regardless of auth status + this.subscribeToNetworkEvents(); + return getAuthPromise() + ?.then((isLoggedIn) => { + if (!isLoggedIn) { + this.handleInsertionIntoDOM(this.embedConfig.loginFailedMessage); + return; + } + this.setIframeElement(this.iFrame || this.createIframeEl(url)); + this.iFrame.addEventListener('load', () => { + nextInQueue(); + const loadTimestamp = Date.now(); + this.executeCallbacks(exports.EmbedEvent.Load, { + data: { + timestamp: loadTimestamp, + }, + type: exports.EmbedEvent.Load, + }); + uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_RENDER_COMPLETE, { + elWidth: this.iFrame.clientWidth, + elHeight: this.iFrame.clientHeight, + timeTookToLoad: loadTimestamp - initTimestamp, + }); + // Send info event if preauth cache is enabled + if (this.isPreAuthCacheEnabled()) { + getPreauthInfo().then((data) => { + if (data?.info) { + this.trigger(exports.HostEvent.InfoSuccess, data); + } + }); + } + // Setup fullscreen change handler after iframe is + // loaded and ready + this.setupFullscreenChangeHandler(); + }); + this.iFrame.addEventListener('error', () => { + nextInQueue(); + }); + this.handleInsertionIntoDOM(this.iFrame); + const prefetchIframe = document.querySelectorAll('.prefetchIframe'); + if (prefetchIframe.length) { + prefetchIframe.forEach((el) => { + el.remove(); + }); + } + // Subscribe to message events only after successful + // auth and iframe setup + this.subscribeToMessageEvents(); + }) + .catch((error) => { + nextInQueue(); + uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_RENDER_FAILED, { + error: JSON.stringify(error), + }); + this.handleInsertionIntoDOM(this.embedConfig.loginFailedMessage); + this.handleError({ + errorType: ErrorDetailsTypes.API, + message: error.message || ERROR_MESSAGE.LOGIN_FAILED, + code: EmbedErrorCodes.LOGIN_FAILED, + error: error, + }); + }); + }); + } + createPreRenderWrapper() { + const preRenderIds = this.getPreRenderIds(); + document.getElementById(preRenderIds.wrapper)?.remove(); + const preRenderWrapper = document.createElement('div'); + preRenderWrapper.id = preRenderIds.wrapper; + const initialPreRenderWrapperStyle = { + position: 'absolute', + top: '0', + left: '0', + width: '100vw', + height: '100vh', + }; + setStyleProperties(preRenderWrapper, initialPreRenderWrapperStyle); + return preRenderWrapper; + } + /** + * Checks for an existing pre-rendered component and connects to it. + * + * If a matching pre-rendered component is found in the DOM, this method + * sets the internal properties of the embed object to reference it. + * + * @returns True if a connection was successfully established, false otherwise. + */ + connectPreRendered() { + const preRenderIds = this.getPreRenderIds(); + const preRenderWrapperElement = document.getElementById(preRenderIds.wrapper); + this.preRenderWrapper = this.preRenderWrapper || preRenderWrapperElement; + this.preRenderChild = this.preRenderChild || document.getElementById(preRenderIds.child); + if (this.preRenderWrapper && this.preRenderChild) { + this.isPreRendered = true; + if (this.preRenderChild instanceof HTMLIFrameElement) { + this.setIframeElement(this.preRenderChild); + } + this.isRendered = true; + } + return this.isPreRenderConnected(); + } + isPreRenderConnected() { + return (Boolean(this.preRenderWrapper && this.preRenderChild)); + } + createPreRenderChild(child) { + const preRenderIds = this.getPreRenderIds(); + document.getElementById(preRenderIds.child)?.remove(); + if (child instanceof HTMLElement) { + child.id = preRenderIds.child; + return child; + } + const divChildNode = document.createElement('div'); + setStyleProperties(divChildNode, { width: '100%', height: '100%' }); + divChildNode.id = preRenderIds.child; + if (typeof child === 'string') { + divChildNode.innerHTML = child; + } + else { + divChildNode.appendChild(child); + } + return divChildNode; + } + /** + * Creates the in-flow placeholder div inserted into the host element when + * showPreRender() is called. The wrapper observes this element to stay + * aligned with the host layout. + */ + createPreRenderPlaceholder() { + const placeholder = document.createElement('div'); + const id = this.getPreRenderIds(); + const { width: frameWidth, height: frameHeight } = this.viewConfig.frameParams || {}; + const width = getCssDimension(frameWidth || DEFAULT_EMBED_WIDTH); + const height = getCssDimension(frameHeight || DEFAULT_EMBED_HEIGHT); + placeholder.style.width = width; + placeholder.style.height = height; + // we can improve this , lol + placeholder.id = id.placeHolder; + return placeholder; + } + insertIntoDOMForPreRender(child) { + const preRenderChild = this.createPreRenderChild(child); + const preRenderWrapper = this.createPreRenderWrapper(); + preRenderWrapper.appendChild(preRenderChild); + this.preRenderChild = preRenderChild; + this.preRenderWrapper = preRenderWrapper; + if (preRenderChild instanceof HTMLIFrameElement) { + this.setIframeElement(preRenderChild); + } + if (this.iFrame) { + this.iFrame.style.height = '100%'; + this.iFrame.style.width = '100%'; + } + if (this.showPreRenderByDefault) { + this.showPreRender(); + } + else { + this.hidePreRender(); + } + document.body.appendChild(preRenderWrapper); + } + insertIntoDOM(child) { + if (this.viewConfig.insertAsSibling) { + if (typeof child === 'string') { + const div = document.createElement('div'); + div.innerHTML = child; + div.id = TS_EMBED_ID; + child = div; + } + if (this.hostElement.nextElementSibling?.id === TS_EMBED_ID) { + this.hostElement.nextElementSibling.remove(); + } + this.hostElement.parentElement.insertBefore(child, this.hostElement.nextSibling); + this.insertedDomEl = child; + } + else if (typeof child === 'string') { + this.hostElement.innerHTML = child; + this.insertedDomEl = this.hostElement.children[0]; + } + else { + this.hostElement.innerHTML = ''; + this.hostElement.appendChild(child); + this.insertedDomEl = child; + } + } + /** + * Sets the height of the iframe + * @param height The height in pixels + */ + setIFrameHeight(height) { + if (this.isPreRendered) { + if (this.insertedDomEl) + this.insertedDomEl.style.height = getCssDimension(height); + else + this.preRenderWrapper.style.height = getCssDimension(height); + } + else { + // normal (non-preRender) mode: size the iframe directly + this.iFrame.style.height = getCssDimension(height); + } + } + shouldSkipEvent(eventType, data) { + const errorType = data?.errorType ?? data?.data?.code; + if (eventType === exports.EmbedEvent.Error + && errorType === EmbedErrorCodes.HOST_EVENT_VALIDATION + && (!getHostEventsConfig(this.viewConfig).useHostEventsV2 || getHostEventsConfig(this.viewConfig).shouldBypassPayloadValidation)) { + logger$3.warn(`Host Event Validation failed: ${data?.data?.message}`); + return true; + } + return false; + } + /** + * Executes all registered event handlers for a particular event type + * @param eventType The event type + * @param data The payload invoked with the event handler + * @param eventPort The event Port for a specific MessageChannel + */ + executeCallbacks(eventType, data, eventPort) { + if (this.shouldSkipEvent(eventType, data)) + return; + const eventHandlers = this.eventHandlerMap.get(eventType) || []; + const allHandlers = this.eventHandlerMap.get(exports.EmbedEvent.ALL) || []; + const callbacks = [...eventHandlers, ...allHandlers]; + const dataStatus = data?.status || embedEventStatus.END; + callbacks.forEach((callbackObj) => { + if ( + // When start status is true it trigger only start releated + // payload + (callbackObj.options.start && dataStatus === embedEventStatus.START) + // When start status is false it trigger only end releated + // payload + || (!callbackObj.options.start && dataStatus === embedEventStatus.END)) { + const responder = this.createEmbedEventResponder(eventPort, eventType); + callbackObj.callback(data, responder); + } + }); + } + /** + * Returns the ThoughtSpot hostname or IP address. + */ + getThoughtSpotHost() { + return this.thoughtSpotHost; + } + /** + * Gets the v1 event type (if applicable) for the EmbedEvent type + * @param eventType The v2 event type + * @returns The corresponding v1 event type if one exists + * or else the v2 event type itself + */ + getCompatibleEventType(eventType) { + return V1EventMap[eventType] || eventType; + } + /** + * Calculates the iframe center for the current visible viewPort + * of iframe using Scroll position of Host App, offsetTop for iframe + * in Host app. ViewPort height of the tab. + * @returns iframe Center in visible viewport, + * Iframe height, + * View port height. + */ + getIframeCenter() { + const offsetTopClient = getOffsetTop(this.iFrame); + const scrollTopClient = window.scrollY; + const viewPortHeight = window.innerHeight; + const iframeHeight = this.iFrame.offsetHeight; + const iframeScrolled = scrollTopClient - offsetTopClient; + let iframeVisibleViewPort; + let iframeOffset; + if (iframeScrolled < 0) { + iframeVisibleViewPort = viewPortHeight - (offsetTopClient - scrollTopClient); + iframeVisibleViewPort = Math.min(iframeHeight, iframeVisibleViewPort); + iframeOffset = 0; + } + else { + iframeVisibleViewPort = Math.min(iframeHeight - iframeScrolled, viewPortHeight); + iframeOffset = iframeScrolled; + } + const iframeCenter = iframeOffset + iframeVisibleViewPort / 2; + return { + iframeCenter, + iframeScrolled, + iframeHeight, + viewPortHeight, + iframeVisibleViewPort, + }; + } + /** + * Registers an event listener to trigger an alert when the ThoughtSpot app + * sends an event of a particular message type to the host application. + * @param messageType The message type + * @param callback A callback as a function + * @param options The message options + * @param isSelf + * @param isRegisteredBySDK + * @example + * ```js + * tsEmbed.on(EmbedEvent.Error, (data) => { + * console.error(data); + * }); + * ``` + * @example + * ```js + * tsEmbed.on(EmbedEvent.Save, (data) => { + * console.log("Answer save clicked", data); + * }, { + * start: true // This will trigger the callback on start of save + * }); + * ``` + */ + on(messageType, callback, options = { start: false }, isRegisteredBySDK = false) { + uploadMixpanelEvent(`${MIXPANEL_EVENT.VISUAL_SDK_ON}-${messageType}`, { + isRegisteredBySDK, + }); + if (this.isRendered) { + logger$3.warn('Please register event handlers before calling render'); + } + const callbacks = this.eventHandlerMap.get(messageType) || []; + callbacks.push({ options, callback }); + this.eventHandlerMap.set(messageType, callbacks); + return this; + } + /** + * Removes an event listener for a particular event type. + * @param messageType The message type + * @param callback The callback to remove + * @example + * ```js + * const errorHandler = (data) => { console.error(data); }; + * tsEmbed.on(EmbedEvent.Error, errorHandler); + * tsEmbed.off(EmbedEvent.Error, errorHandler); + * ``` + */ + off(messageType, callback) { + const callbacks = this.eventHandlerMap.get(messageType) || []; + const index = callbacks.findIndex((cb) => cb.callback === callback); + if (index > -1) { + callbacks.splice(index, 1); + } + return this; + } + /** + * Triggers an event on specific Port registered against + * for the EmbedEvent + * @param eventType The message type + * @param data The payload to send + * @param eventPort + * @param payload + */ + triggerEventOnPort(eventPort, payload) { + if (eventPort) { + try { + eventPort.postMessage({ + type: payload.type, + data: payload.data, + }); + } + catch (e) { + eventPort.postMessage({ error: e }); + logger$3.log(e); + } + } + else { + logger$3.log('Event Port is not defined'); + } + } + getPreRenderObj() { + const embedObj = this.preRenderWrapper?.[this.embedNodeKey]; + if (embedObj === this) { + logger$3.info('embedObj is same as this'); + } + return embedObj; + } + checkEmbedContainerLoaded() { + if (this.isEmbedContainerLoaded) + return true; + const preRenderObj = this.getPreRenderObj(); + if (preRenderObj && preRenderObj.isEmbedContainerLoaded) { + this.isEmbedContainerLoaded = true; + } + return this.isEmbedContainerLoaded; + } + executeEmbedContainerReadyCallbacks() { + logger$3.debug('executePendingEvents', this.embedContainerReadyCallbacks); + this.embedContainerReadyCallbacks.forEach((callback) => { + callback?.(); + }); + this.embedContainerReadyCallbacks = []; + } + /** + * Executes a callback after the embed container is loaded. + * @param callback The callback to execute + */ + executeAfterEmbedContainerLoaded(callback) { + if (this.checkEmbedContainerLoaded()) { + callback?.(); + } + else { + logger$3.debug('pushing callback to embedContainerReadyCallbacks', callback); + this.embedContainerReadyCallbacks.push(callback); + } + } + /** + * Triggers an event to the embedded app + * @param {HostEvent} messageType The event type + * @param {any} data The payload to send with the message + * @param {ContextType} context Optional context type to specify the context from which the event is triggered. + * Use ContextType.Search for search answer context, ContextType.Answer for answer/explore context, + * ContextType.Liveboard for liveboard context, or ContextType.Spotter for spotter context. + * Available from SDK version 1.45.2 | ThoughtSpot: 26.3.0.cl + * @returns A promise that resolves with the response from the embedded app + * @example + * ```js + * // Trigger Pin event with context (SDK: 1.45.2+) + * import { HostEvent, ContextType } from '@thoughtspot/visual-embed-sdk'; + * embed.trigger(HostEvent.Pin, { + * vizId: "123", + * liveboardId: "456" + * }, ContextType.Search); + * ``` + * @version SDK: 1.45.2 | ThoughtSpot: 26.3.0.cl (for context parameter) + */ + async trigger(messageType, data = {}, context) { + uploadMixpanelEvent(`${MIXPANEL_EVENT.VISUAL_SDK_TRIGGER}-${messageType}`); + if (!this.isRendered) { + this.handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.RENDER_BEFORE_EVENTS_REQUIRED, + code: EmbedErrorCodes.RENDER_NOT_CALLED, + error: ERROR_MESSAGE.RENDER_BEFORE_EVENTS_REQUIRED, + }); + return null; + } + if (!messageType) { + this.handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.HOST_EVENT_TYPE_UNDEFINED, + code: EmbedErrorCodes.HOST_EVENT_TYPE_UNDEFINED, + error: ERROR_MESSAGE.HOST_EVENT_TYPE_UNDEFINED, + }); + return null; + } + // Check if iframe exists before triggering - + // this prevents the error when auth fails + if (!this.iFrame) { + logger$3.debug(`Cannot trigger ${messageType} - iframe not available (likely due to auth failure)`); + return null; + } + // send an empty object, this is needed for liveboard default handlers + return this.hostEventClient.triggerHostEvent(messageType, data, context).catch((err) => { + if (err?.isValidationError) { + const errorDetails = err.embedErrorDetails ?? { + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: err.message || ERROR_MESSAGE.UPDATEFILTERS_INVALID_PAYLOAD, + code: EmbedErrorCodes.UPDATEFILTERS_INVALID_PAYLOAD, + error: err.message, + }; + this.handleError(errorDetails); + } + throw err; + }); + } + /** + * Triggers an event to the embedded app, skipping the UI flow. + * @param {UIPassthroughEvent} apiName - The name of the API to be triggered. + * @param {UIPassthroughRequest} parameters - The parameters to be passed to the API. + * @returns {Promise} - A promise that resolves with the response + * from the embedded app. + */ + async triggerUIPassThrough(apiName, parameters) { + const response = this.hostEventClient.triggerUIPassthroughApi(apiName, parameters); + return response; + } + /** + * Marks the ThoughtSpot object to have been rendered + * Needs to be overridden by subclasses to do the actual + * rendering of the iframe. + * @param args + */ + async render() { + if (!getIsInitCalled()) { + logger$3.error(ERROR_MESSAGE.RENDER_CALLED_BEFORE_INIT); + } + if (this.shouldWaitForRenderPromise) + await this.isReadyForRenderPromise; + this.isRendered = true; + return this; + } + getIframeSrc() { + return ''; + } + handleRenderForPrerender() { + return this.render(); + } + /** + * Context object for the embedded component. + * @returns {ContextObject} The current context object containing the page type and object ids. + * @example + * ```js + * const context = await embed.getCurrentContext(); + * console.log(context); + * + * // Example output + * { + * stack: [ + * { + * name: 'Liveboard', + * type: ContextType.Liveboard, + * objectIds: { + * liveboardId: '123', + * }, + * }, + * ], + * currentContext: { + * name: 'Liveboard', + * type: ContextType.Liveboard, + * objectIds: { + * liveboardId: '123', + * }, + * }, + * } + * ``` + * @version SDK: 1.45.2 | ThoughtSpot: 26.3.0.cl + */ + async getCurrentContext() { + return new Promise((resolve) => { + this.executeAfterEmbedContainerLoaded(async () => { + const context = await this.trigger(exports.HostEvent.GetPageContext, {}); + resolve(context); + }); + }); + } + /** + * Generates the event name for a "Subscribed" embed event. + * + * This helper appends the "Subscribed" suffix to a given host or action event, + * allowing you to listen for subscription lifecycle events in a consistent format. + * + * @param eventName - The host or action event to generate the subscribed event name for. + * @returns The formatted event name (e.g., "Save Subscribed"). + * + * @version SDK: 1.47.2 | ThoughtSpot: 26.3.0.cl + */ + subscribedEvent(eventName) { + return `${eventName} ${exports.EmbedEvent.Subscribed}`; + } + /** + * Creates the preRender shell + * @param showPreRenderByDefault - Show the preRender after render, hidden by default + */ + async preRender(showPreRenderByDefault = false, replaceExistingPreRender = false) { + if (!this.viewConfig.preRenderId) { + logger$3.error(ERROR_MESSAGE.PRERENDER_ID_MISSING); + return this; + } + this.isPreRendered = true; + this.showPreRenderByDefault = showPreRenderByDefault; + const isAlreadyRendered = this.connectPreRendered(); + if (isAlreadyRendered && !replaceExistingPreRender) { + if (this.showPreRenderByDefault) { + this.showPreRender(); + } + return this; + } + return this.handleRenderForPrerender(); + } + /** + * Get the Post Url Params for THOUGHTSPOT from the current + * host app URL. + * THOUGHTSPOT URL params starts with a prefix "ts-" + * @version SDK: 1.14.0 | ThoughtSpot: 8.4.0.cl, 8.4.1-sw + */ + getThoughtSpotPostUrlParams(additionalParams = {}) { + const urlHash = window.location.hash; + const queryParams = window.location.search; + const postHashParams = urlHash.split('?'); + const postURLParams = postHashParams[postHashParams.length - 1]; + const queryParamsObj = new URLSearchParams(queryParams); + const postURLParamsObj = new URLSearchParams(postURLParams); + const params = new URLSearchParams(); + const addKeyValuePairCb = (value, key) => { + if (key.startsWith(THOUGHTSPOT_PARAM_PREFIX)) { + params.append(key, value); + } + }; + queryParamsObj.forEach(addKeyValuePairCb); + postURLParamsObj.forEach(addKeyValuePairCb); + Object.entries(additionalParams).forEach(([k, v]) => params.append(k, v)); + let tsParams = params.toString(); + tsParams = tsParams ? `?${tsParams}` : ''; + return tsParams; + } + /** + * Destroys the ThoughtSpot embed, and remove any nodes from the DOM. + * @version SDK: 1.19.1 | ThoughtSpot: * + */ + destroy() { + try { + this.removeFullscreenChangeHandler(); + this.unsubscribeToEvents(); + this.preRenderWrapper?.remove(); + if (!this.isRendered) { + return; + } + if (!getEmbedConfig().waitForCleanupOnDestroy) { + this.trigger(exports.HostEvent.DestroyEmbed); + this.insertedDomEl?.parentNode?.removeChild(this.insertedDomEl); + } + else { + const cleanupTimeout = getEmbedConfig().cleanupTimeout; + Promise.race([ + this.trigger(exports.HostEvent.DestroyEmbed), + new Promise((resolve) => setTimeout(resolve, cleanupTimeout)), + ]).catch((e) => { + logger$3.log('Error destroying TS Embed', e); + }).finally(() => { + try { + this.insertedDomEl?.parentNode?.removeChild(this.insertedDomEl); + } + catch (e) { + logger$3.log('Error removing DOM element on destroy', e); + } + }); + } + } + catch (e) { + logger$3.log('Error destroying TS Embed', e); + } + } + getUnderlyingFrameElement() { + return this.iFrame; + } + /** + * Prerenders a generic instance of the TS component. + * This means without the path but with the flags already applied. + * This is useful for prerendering the component in the background. + * @version SDK: 1.22.0 + * @returns + */ + async prerenderGeneric() { + if (!getIsInitCalled()) { + logger$3.error(ERROR_MESSAGE.RENDER_CALLED_BEFORE_INIT); + } + if (this.shouldWaitForRenderPromise) + await this.isReadyForRenderPromise; + const prerenderFrameSrc = this.getRootIframeSrc(); + this.isRendered = true; + return this.renderIFrame(prerenderFrameSrc); + } + beforePrerenderVisible() { + // We can ignore this as its a bit expensive and the newer customers + // have moved on to UpdateEmbedParams supported clusters + // this.validatePreRenderViewConfig(this.viewConfig); removed in #517 + logger$3.debug('triggering UpdateEmbedParams', this.viewConfig); + this.executeAfterEmbedContainerLoaded(async () => { + try { + const params = await this.getUpdateEmbedParamsObject(); + this.trigger(exports.HostEvent.UpdateEmbedParams, params); + } + catch (error) { + logger$3.error(ERROR_MESSAGE.UPDATE_PARAMS_FAILED, error); + this.handleError({ + errorType: ErrorDetailsTypes.API, + message: error?.message || ERROR_MESSAGE.UPDATE_PARAMS_FAILED, + code: EmbedErrorCodes.UPDATE_PARAMS_FAILED, + error: error?.message || error, + }); + } + }); + } + /** + * Displays the pre-rendered component inside the host element. + * If the component has not been pre-rendered yet, it initiates rendering first. + * Inserts a placeholder element into the host and positions the pre-render + * wrapper to overlay it. + */ + async showPreRender() { + if (this.shouldWaitForRenderPromise) + await this.isReadyForRenderPromise; + if (!this.viewConfig.preRenderId) { + logger$3.error(ERROR_MESSAGE.PRERENDER_ID_MISSING); + return this; + } + if (!this.isPreRenderConnected()) { + // this will call showPreRender down the line + return this.preRender(true); + } + this.isRendered = true; + this.beforePrerenderVisible(); + if (this.hostElement) { + this.insertedDomEl = this.createPreRenderPlaceholder(); + if (this.viewConfig.fullHeight) { + // If fullHeight has already sized the wrapper, seed the placeholder + // with the same height so syncPreRenderStyle gets an accurate rect. + const existingHeight = this.preRenderWrapper.style.height; + if (existingHeight) { + this.insertedDomEl.style.height = existingHeight; + } + } + const placeHolderId = this.getPreRenderIds().placeHolder; + const oldEle = this.hostElement.querySelector(`#${placeHolderId}`); + if (oldEle) { + this.hostElement.removeChild(oldEle); + } + this.hostElement.appendChild(this.insertedDomEl); + this.syncPreRenderStyle(); + if (!this.viewConfig.doNotTrackPreRenderSize) { + const observeTarget = this.insertedDomEl ?? this.hostElement; + this.resizeObserver = new ResizeObserver((entries) => { + entries.forEach((entry) => { + if (entry.contentRect && entry.target === observeTarget) { + setStyleProperties(this.preRenderWrapper, { + width: `${entry.contentRect.width}px`, + height: `${entry.contentRect.height}px`, + }); + } + }); + }); + this.resizeObserver.observe(observeTarget); + } + } + removeStyleProperties(this.preRenderWrapper, ['z-index', 'opacity', 'pointer-events', 'overflow']); + this.subscribeToEvents(); + // Setup fullscreen change handler for prerendered components + if (this.iFrame) { + this.setupFullscreenChangeHandler(); + } + return this; + } + getPreRenderPlaceHolderElement() { + return this.insertedDomEl; + } + /** + * Synchronizes the style properties of the PreRender component with the embedding + * element. This function adjusts the position, width, and height of the PreRender + * component + * to match the dimensions and position of the embedding element. + * @throws {Error} Throws an error if the embedding element (passed as domSelector) + * is not defined or not found. + */ + syncPreRenderStyle() { + if (!this.isPreRenderConnected() || !this.getPreRenderPlaceHolderElement()) { + logger$3.error(ERROR_MESSAGE.SYNC_STYLE_CALLED_BEFORE_RENDER); + return; + } + const elBoundingClient = this.getPreRenderPlaceHolderElement().getBoundingClientRect(); + setStyleProperties(this.preRenderWrapper, { + top: `${elBoundingClient.y + window.scrollY}px`, + left: `${elBoundingClient.x + window.scrollX}px`, + width: `${elBoundingClient.width}px`, + height: `${elBoundingClient.height}px`, + position: 'absolute' + }); + } + /** + * Hides the PreRender component if it is available. + * If the component is not preRendered, it issues a warning. + */ + hidePreRender() { + logger$3.debug('HidePreRender Called'); + if (!this.isPreRenderConnected()) { + // if the embed component is not preRendered , nothing to hide + logger$3.warn('PreRender should be called before hiding it using hidePreRender.'); + return; + } + const preRenderHideStyles = { + opacity: '0', + pointerEvents: 'none', + zIndex: '-1000', + position: 'absolute', + top: '0', + left: '0', + overflow: 'hidden', + }; + setStyleProperties(this.preRenderWrapper, preRenderHideStyles); + if (this.resizeObserver) { + this.resizeObserver.disconnect(); + } + const placeHolderEle = this.getPreRenderPlaceHolderElement(); + if (placeHolderEle) { + placeHolderEle.parentElement.removeChild(placeHolderEle); + } + this.unsubscribeToEvents(); + } + /** + * Retrieves unique HTML element IDs for PreRender-related elements. + * These IDs are constructed based on the provided 'preRenderId' from 'viewConfig'. + * @returns {object} An object containing the IDs for the PreRender elements. + * @property {string} wrapper - The HTML element ID for the PreRender wrapper. + * @property {string} child - The HTML element ID for the PreRender child. + */ + getPreRenderIds() { + return { + wrapper: `tsEmbed-pre-render-wrapper-${this.viewConfig.preRenderId}`, + child: `tsEmbed-pre-render-child-${this.viewConfig.preRenderId}`, + placeHolder: `tsEmbed-pre-render-placeholder-${this.viewConfig.preRenderId}`, + }; + } + /** + * Returns the answerService which can be used to make arbitrary graphql calls on top + * session. + * @param vizId [Optional] to get for a specific viz in case of a Liveboard. + * @version SDK: 1.25.0 | ThoughtSpot: 9.10.0 + */ + async getAnswerService(vizId) { + const { session } = await this.trigger(exports.HostEvent.GetAnswerSession, vizId ? { vizId } : {}); + return new AnswerService(session, null, this.embedConfig.thoughtSpotHost); + } + /** + * Set up fullscreen change detection to automatically trigger ExitPresentMode + * when user exits fullscreen mode + */ + setupFullscreenChangeHandler() { + const embedConfig = getEmbedConfig(); + const disableFullscreenPresentation = embedConfig?.disableFullscreenPresentation ?? true; + if (disableFullscreenPresentation) { + return; + } + if (this.fullscreenChangeHandler) { + document.removeEventListener('fullscreenchange', this.fullscreenChangeHandler); + } + this.fullscreenChangeHandler = () => { + const isFullscreen = !!document.fullscreenElement; + if (!isFullscreen) { + logger$3.info('Exited fullscreen mode - triggering ExitPresentMode'); + // Only trigger if iframe is available and contentWindow is + // accessible + if (this.iFrame && this.iFrame.contentWindow) { + this.trigger(exports.HostEvent.ExitPresentMode); + } + else { + logger$3.debug('Skipping ExitPresentMode - iframe contentWindow not available'); + } + } + }; + document.addEventListener('fullscreenchange', this.fullscreenChangeHandler); + } + /** + * Remove fullscreen change handler + */ + removeFullscreenChangeHandler() { + if (this.fullscreenChangeHandler) { + document.removeEventListener('fullscreenchange', this.fullscreenChangeHandler); + this.fullscreenChangeHandler = null; + } + } + } + /** + * Base class for embedding v1 experience + * Note: The v1 version of ThoughtSpot Blink works on the AngularJS stack + * which is currently under migration to v2 + * @inheritdoc + */ + class V1Embed extends TsEmbed { + constructor(domSelector, viewConfig) { + super(domSelector, viewConfig); + /** + * Only for testing purposes. + * @hidden + */ + this.test__executeCallbacks = this.executeCallbacks; + this.viewConfig = { + excludeRuntimeFiltersfromURL: true, + excludeRuntimeParametersfromURL: true, + ...viewConfig, + }; + } + /** + * Render the app in an iframe and set up event handlers + * @param iframeSrc + */ + renderV1Embed(iframeSrc) { + return this.renderIFrame(iframeSrc); + } + getRootIframeSrc() { + const queryParams = this.getEmbedParams(); + let queryString = queryParams; + if (!this.viewConfig.excludeRuntimeParametersfromURL) { + const runtimeParameters = this.viewConfig.runtimeParameters; + const parameterQuery = getRuntimeParameters(runtimeParameters || []); + queryString = [parameterQuery, queryParams].filter(Boolean).join('&'); + } + if (!this.viewConfig.excludeRuntimeFiltersfromURL) { + const runtimeFilters = this.viewConfig.runtimeFilters; + const filterQuery = getFilterQuery(runtimeFilters || []); + queryString = [filterQuery, queryString].filter(Boolean).join('&'); + } + return this.viewConfig.enableV2Shell_experimental + ? this.getEmbedBasePath(queryString) + : this.getV1EmbedBasePath(queryString); + } + /** + * @inheritdoc + * @example + * ```js + * tsEmbed.on(EmbedEvent.Error, (data) => { + * console.error(data); + * }); + * ``` + * @example + * ```js + * tsEmbed.on(EmbedEvent.Save, (data) => { + * console.log("Answer save clicked", data); + * }, { + * start: true // This will trigger the callback on start of save + * }); + * ``` + */ + on(messageType, callback, options = { start: false }) { + const eventType = this.getCompatibleEventType(messageType); + return super.on(eventType, callback, options); + } + } + + /** + * Embed ThoughtSpot search bar + * @version SDK: 1.18.0 | ThoughtSpot: 8.10.0.cl, 9.0.1-sw + * @group Embed components + */ + let SearchBarEmbed$1 = class SearchBarEmbed extends TsEmbed { + constructor(domSelector, viewConfig) { + super(domSelector); + this.embedComponentType = 'SearchBarEmbed'; + this.viewConfig = viewConfig; + } + getEmbedParamsObject() { + const { searchOptions, dataSource, dataSources, useLastSelectedSources = false, excludeSearchTokenStringFromURL, } = this.viewConfig; + const queryParams = this.getBaseQueryParams(); + queryParams[Param.HideActions] = [...(queryParams[Param.HideActions] ?? [])]; + if (dataSources && dataSources.length) { + queryParams[Param.DataSources] = JSON.stringify(dataSources); + } + if (dataSource) { + queryParams[Param.DataSources] = `["${dataSource}"]`; + } + if (searchOptions?.searchTokenString) { + if (!excludeSearchTokenStringFromURL) { + queryParams[Param.searchTokenString] = encodeURIComponent(searchOptions.searchTokenString); + } + if (searchOptions.executeSearch) { + queryParams[Param.executeSearch] = true; + } + } + queryParams[Param.UseLastSelectedDataSource] = useLastSelectedSources; + if (dataSource || dataSources) { + queryParams[Param.UseLastSelectedDataSource] = false; + } + queryParams[Param.searchEmbed] = true; + return queryParams; + } + /** + * Construct the URL of the embedded ThoughtSpot search to be + * loaded in the iframe + * @param dataSources A list of data source GUIDs + */ + getIFrameSrc() { + const queryParams = this.getEmbedParamsObject(); + const path = 'search-bar-embed'; + let query = ''; + const queryParamsString = getQueryParamString(queryParams, true); + if (queryParamsString) { + query = `?${queryParamsString}`; + } + const tsPostHashParams = this.getThoughtSpotPostUrlParams(); + return `${this.getEmbedBasePath(query)}/embed/${path}${tsPostHashParams}`; + } + /** + * Render the embedded ThoughtSpot search + */ + async render() { + await super.render(); + const src = this.getIFrameSrc(); + await this.renderIFrame(src); + return this; + } + getSearchInitData() { + return { + searchOptions: this.viewConfig.excludeSearchTokenStringFromURL + ? this.viewConfig.searchOptions + : null, + }; + } + async getAppInitData() { + const defaultAppInitData = await super.getAppInitData(); + return { ...defaultAppInitData, ...this.getSearchInitData() }; + } + }; + + /** + * Copyright (c) 2022 + * + * Embed ThoughtSpot search or a saved answer. + * https://developers.thoughtspot.com/docs/search-embed + * @summary Search embed + * @author Ayon Ghosh + */ + /** + * Define the initial state of column custom group accordions + * in data panel v2. + */ + var DataPanelCustomColumnGroupsAccordionState$1; + (function (DataPanelCustomColumnGroupsAccordionState) { + /** + * Expand all the accordion initially in data panel v2. + */ + DataPanelCustomColumnGroupsAccordionState["EXPAND_ALL"] = "EXPAND_ALL"; + /** + * Collapse all the accordions initially in data panel v2. + */ + DataPanelCustomColumnGroupsAccordionState["COLLAPSE_ALL"] = "COLLAPSE_ALL"; + /** + * Expand the first accordion and collapse the rest. + */ + DataPanelCustomColumnGroupsAccordionState["EXPAND_FIRST"] = "EXPAND_FIRST"; + })(DataPanelCustomColumnGroupsAccordionState$1 || (DataPanelCustomColumnGroupsAccordionState$1 = {})); + const HiddenActionItemByDefaultForSearchEmbed = [ + exports.Action.EditACopy, + exports.Action.SaveAsView, + exports.Action.UpdateTML, + exports.Action.EditTML, + exports.Action.AnswerDelete, + ]; + /** + * Embed ThoughtSpot search + * @group Embed components + */ + let SearchEmbed$1 = class SearchEmbed extends TsEmbed { + constructor(domSelector, viewConfig) { + viewConfig = { + embedComponentType: 'SearchEmbed', + excludeRuntimeFiltersfromURL: true, + excludeRuntimeParametersfromURL: true, + ...viewConfig, + }; + super(domSelector, viewConfig); + } + /** + * Get the state of the data sources panel that the embedded + * ThoughtSpot search will be initialized with. + */ + getDataSourceMode() { + let dataSourceMode = DataSourceVisualMode.Expanded; + if (this.viewConfig.collapseDataSources === true + || this.viewConfig.collapseDataPanel === true) { + dataSourceMode = DataSourceVisualMode.Collapsed; + } + if (this.viewConfig.hideDataSources === true) { + dataSourceMode = DataSourceVisualMode.Hidden; + } + return dataSourceMode; + } + getSearchInitData() { + return { + ...(this.viewConfig.excludeSearchTokenStringFromURL ? { + searchOptions: { + searchTokenString: this.viewConfig.searchOptions?.searchTokenString, + }, + } : {}), + }; + } + async getAppInitData() { + const defaultAppInitData = await super.getAppInitData(); + const result = { + ...defaultAppInitData, + ...this.getSearchInitData(), + }; + if (this.viewConfig.visualOverrides) { + result.embedParams = { + ...(defaultAppInitData.embedParams || {}), + visualOverridesParams: this.viewConfig.visualOverrides, + }; + } + return result; + } + getEmbedParamsObject() { + const { hideResults, enableSearchAssist, forceTable, searchOptions, runtimeFilters, dataSource, dataSources, excludeRuntimeFiltersfromURL, hideSearchBar, dataPanelV2 = true, useLastSelectedSources = false, runtimeParameters, collapseSearchBarInitially = false, enableCustomColumnGroups = false, dataPanelCustomGroupsAccordionInitialState = DataPanelCustomColumnGroupsAccordionState$1.EXPAND_ALL, focusSearchBarOnRender = true, excludeRuntimeParametersfromURL, excludeSearchTokenStringFromURL, collapseSearchBar = true, isThisPeriodInDateFiltersEnabled, newChartsLibrary, } = this.viewConfig; + const queryParams = this.getBaseQueryParams(); + queryParams[Param.HideActions] = [ + ...(queryParams[Param.HideActions] ?? []), + ...HiddenActionItemByDefaultForSearchEmbed, + ]; + if (dataSources && dataSources.length) { + queryParams[Param.DataSources] = JSON.stringify(dataSources); + } + if (dataSource) { + queryParams[Param.DataSources] = `["${dataSource}"]`; + } + if (searchOptions?.searchTokenString) { + if (!excludeSearchTokenStringFromURL) { + queryParams[Param.searchTokenString] = encodeURIComponent(searchOptions.searchTokenString); + } + if (searchOptions.executeSearch) { + queryParams[Param.executeSearch] = true; + } + } + if (enableSearchAssist) { + queryParams[Param.EnableSearchAssist] = true; + } + if (hideResults) { + queryParams[Param.HideResult] = true; + } + if (forceTable) { + queryParams[Param.ForceTable] = true; + } + if (hideSearchBar) { + queryParams[Param.HideSearchBar] = true; + } + if (!focusSearchBarOnRender) { + queryParams[Param.FocusSearchBarOnRender] = focusSearchBarOnRender; + } + if (isThisPeriodInDateFiltersEnabled !== undefined) { + queryParams[Param.IsThisPeriodInDateFiltersEnabled] = isThisPeriodInDateFiltersEnabled; + } + if (newChartsLibrary !== undefined) { + queryParams[Param.EnableNewChartLibrary] = newChartsLibrary; + } + queryParams[Param.DataPanelV2Enabled] = dataPanelV2; + queryParams[Param.DataSourceMode] = this.getDataSourceMode(); + queryParams[Param.UseLastSelectedDataSource] = useLastSelectedSources; + if (dataSource || dataSources) { + queryParams[Param.UseLastSelectedDataSource] = false; + } + queryParams[Param.searchEmbed] = true; + queryParams[Param.CollapseSearchBarInitially] = collapseSearchBarInitially || collapseSearchBar; + queryParams[Param.EnableCustomColumnGroups] = enableCustomColumnGroups; + if (dataPanelCustomGroupsAccordionInitialState + === DataPanelCustomColumnGroupsAccordionState$1.COLLAPSE_ALL + || dataPanelCustomGroupsAccordionInitialState + === DataPanelCustomColumnGroupsAccordionState$1.EXPAND_FIRST) { + queryParams[Param.DataPanelCustomGroupsAccordionInitialState] = dataPanelCustomGroupsAccordionInitialState; + } + else { + queryParams[Param.DataPanelCustomGroupsAccordionInitialState] = DataPanelCustomColumnGroupsAccordionState$1.EXPAND_ALL; + } + return queryParams; + } + getEmbedParams() { + const { runtimeParameters, runtimeFilters, excludeRuntimeParametersfromURL, excludeRuntimeFiltersfromURL, } = this.viewConfig; + const queryParams = this.getEmbedParamsObject(); + let query = ''; + const queryParamsString = getQueryParamString(queryParams, true); + if (queryParamsString) { + query = `?${queryParamsString}`; + } + const parameterQuery = getRuntimeParameters(runtimeParameters || []); + if (parameterQuery && !excludeRuntimeParametersfromURL) + query += `&${parameterQuery}`; + const filterQuery = getFilterQuery(runtimeFilters || []); + if (filterQuery && !excludeRuntimeFiltersfromURL) { + query += `&${filterQuery}`; + } + return query; + } + /** + * Construct the URL of the embedded ThoughtSpot search to be + * loaded in the iframe + * @param answerId The GUID of a saved answer + * @param dataSources A list of data source GUIDs + */ + getIFrameSrc() { + const { answerId } = this.viewConfig; + const answerPath = answerId ? `saved-answer/${answerId}` : 'answer'; + const tsPostHashParams = this.getThoughtSpotPostUrlParams(); + return `${this.getRootIframeSrc()}/embed/${answerPath}${tsPostHashParams}`; + } + /** + * Render the embedded ThoughtSpot search + */ + async render() { + await super.render(); + const { answerId } = this.viewConfig; + const src = this.getIFrameSrc(); + await this.renderIFrame(src); + getAuthPromise().then(() => { + if (checkReleaseVersionInBeta(getReleaseVersion(), getEmbedConfig().suppressSearchEmbedBetaWarning + || getEmbedConfig().suppressErrorAlerts)) { + alert(ERROR_MESSAGE.SEARCHEMBED_BETA_WRANING_MESSAGE); + } + }); + return this; + } + }; + + /** + * Resolves enablePastConversationsSidebar with + * spotterSidebarConfig taking precedence over the + * standalone flag. + */ + const resolveEnablePastConversationsSidebar = (params) => (params.spotterSidebarConfigValue !== undefined + ? params.spotterSidebarConfigValue + : params.standaloneValue); + function buildSpotterSidebarAppInitData(defaultAppInitData, viewConfig, handleError) { + const { spotterSidebarConfig, enablePastConversationsSidebar, visualOverrides } = viewConfig; + const resolvedEnablePastConversations = resolveEnablePastConversationsSidebar({ + spotterSidebarConfigValue: spotterSidebarConfig?.enablePastConversationsSidebar, + standaloneValue: enablePastConversationsSidebar, + }); + const hasConfig = spotterSidebarConfig || resolvedEnablePastConversations !== undefined; + if (!hasConfig) { + if (visualOverrides === undefined) { + return defaultAppInitData; + } + return { + ...defaultAppInitData, + embedParams: { visualOverridesParams: visualOverrides }, + }; + } + const resolvedSidebarConfig = { + ...spotterSidebarConfig, + ...(resolvedEnablePastConversations !== undefined && { + enablePastConversationsSidebar: resolvedEnablePastConversations, + }), + }; + if (resolvedSidebarConfig.spotterDocumentationUrl !== undefined) { + const [isValid, validationError] = validateHttpUrl(resolvedSidebarConfig.spotterDocumentationUrl); + if (!isValid) { + handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.INVALID_SPOTTER_DOCUMENTATION_URL, + code: EmbedErrorCodes.INVALID_URL, + error: validationError?.message || ERROR_MESSAGE.INVALID_SPOTTER_DOCUMENTATION_URL, + }); + delete resolvedSidebarConfig.spotterDocumentationUrl; + } + } + return { + ...defaultAppInitData, + embedParams: { + ...(defaultAppInitData.embedParams || {}), + spotterSidebarConfig: resolvedSidebarConfig, + ...(visualOverrides !== undefined ? { visualOverridesParams: visualOverrides } : {}), + }, + }; + } + + function buildSpotterVizAppInitData(initData, viewConfig) { + const { spotterViz } = viewConfig; + if (!spotterViz) + return initData; + return { + ...initData, + embedParams: { + ...(initData.embedParams || {}), + spotterVizConfig: spotterViz, + }, + }; + } + + /** + * Copyright (c) 2022 + * + * Full application embedding + * https://developers.thoughtspot.com/docs/?pageid=full-embed + * @summary Full app embed + * @module + * @author Ayon Ghosh + */ + /** + * Pages within the ThoughtSpot app that can be embedded. + */ + exports.Page = void 0; + (function (Page) { + /** + * Home page + */ + Page["Home"] = "home"; + /** + * Search page + */ + Page["Search"] = "search"; + /** + * Saved answers listing page + */ + Page["Answers"] = "answers"; + /** + * Liveboards listing page + */ + Page["Liveboards"] = "liveboards"; + /** + * @hidden + */ + Page["Pinboards"] = "pinboards"; + /** + * Data management page + */ + Page["Data"] = "data"; + /** + * SpotIQ listing page + */ + Page["SpotIQ"] = "insights"; + /** + * Monitor Alerts Page + */ + Page["Monitor"] = "monitor"; + })(exports.Page || (exports.Page = {})); + /** + * Define the initial state of column custom group accordions + * in data panel v2. + */ + var DataPanelCustomColumnGroupsAccordionState; + (function (DataPanelCustomColumnGroupsAccordionState) { + /** + * Expand all the accordion initially in data panel v2. + */ + DataPanelCustomColumnGroupsAccordionState["EXPAND_ALL"] = "EXPAND_ALL"; + /** + * Collapse all the accordions initially in data panel v2. + */ + DataPanelCustomColumnGroupsAccordionState["COLLAPSE_ALL"] = "COLLAPSE_ALL"; + /** + * Expand the first accordion and collapse the rest. + */ + DataPanelCustomColumnGroupsAccordionState["EXPAND_FIRST"] = "EXPAND_FIRST"; + })(DataPanelCustomColumnGroupsAccordionState || (DataPanelCustomColumnGroupsAccordionState = {})); + var HomePageSearchBarMode; + (function (HomePageSearchBarMode) { + HomePageSearchBarMode["OBJECT_SEARCH"] = "objectSearch"; + HomePageSearchBarMode["AI_ANSWER"] = "aiAnswer"; + HomePageSearchBarMode["NONE"] = "none"; + })(HomePageSearchBarMode || (HomePageSearchBarMode = {})); + /** + * Define the version of the primary navbar + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + var PrimaryNavbarVersion; + (function (PrimaryNavbarVersion) { + /** + * Sliding (v3) introduces a new left-side navigation hub featuring a tab switcher, + * along with updates to the top navigation bar. + * It serves as the foundational version of the PrimaryNavBar. + */ + PrimaryNavbarVersion["Sliding"] = "v3"; + })(PrimaryNavbarVersion || (PrimaryNavbarVersion = {})); + /** + * Define the version of the home page + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + var HomePage; + (function (HomePage) { + /** + * Modular (v2) introduces the updated Modular Home Experience. + * It serves as the foundational version of the home page. + */ + HomePage["Modular"] = "v2"; + /** + * ModularWithStylingChanges (v3) introduces Modular Home Experience + * with styling changes. + */ + HomePage["ModularWithStylingChanges"] = "v3"; + /** + * Focused (v4) introduces the V4 homepage experience + * in which Watchlist and recents and incorporated together + * to form a more focused homepage. + * Pre-requisite : spotter enablement + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + */ + HomePage["Focused"] = "v4"; + })(HomePage || (HomePage = {})); + /** + * Define the version of the list page + * @version SDK: 1.40.0 | ThoughtSpot: 10.12.0.cl + */ + var ListPage; + (function (ListPage) { + /** + * List (v2) is the traditional List Experience. + * It serves as the foundational version of the list page. + */ + ListPage["List"] = "v2"; + /** + * ListWithUXChanges (v3) introduces the new updated list page with UX changes. + */ + ListPage["ListWithUXChanges"] = "v3"; + })(ListPage || (ListPage = {})); + /** + * Embeds full ThoughtSpot experience in a host application. + * @group Embed components + */ + let AppEmbed$1 = class AppEmbed extends V1Embed { + constructor(domSelector, viewConfig) { + viewConfig.embedComponentType = 'AppEmbed'; + super(domSelector, viewConfig); + this.defaultHeight = 500; + this.sendFullHeightLazyLoadData = () => { + const data = calculateVisibleElementData(this.iFrame); + // this should be fired only if the lazyLoadingForFullHeight and fullHeight are true + if (this.viewConfig.lazyLoadingForFullHeight && this.viewConfig.fullHeight) { + this.trigger(exports.HostEvent.VisibleEmbedCoordinates, data); + } + }; + /** + * This is a handler for the RequestVisibleEmbedCoordinates event. + * It is used to send the visible coordinates data to the host application. + * @param data The event payload + * @param responder The responder function + */ + this.requestVisibleEmbedCoordinatesHandler = (data, responder) => { + logger$3.info('Sending RequestVisibleEmbedCoordinates', data); + const visibleCoordinatesData = calculateVisibleElementData(this.iFrame); + responder({ type: exports.EmbedEvent.RequestVisibleEmbedCoordinates, data: visibleCoordinatesData }); + }; + /** + * Set the iframe height as per the computed height received + * from the ThoughtSpot app. + * @param data The event payload + */ + this.updateIFrameHeight = (data) => { + this.setIFrameHeight(Math.max(data.data, this.defaultHeight)); + this.sendFullHeightLazyLoadData(); + }; + this.embedIframeCenter = (data, responder) => { + const obj = this.getIframeCenter(); + responder({ type: exports.EmbedEvent.EmbedIframeCenter, data: obj }); + }; + this.setIframeHeightForNonEmbedLiveboard = (data) => { + const { height: frameHeight } = this.viewConfig.frameParams || {}; + const liveboardRelatedRoutes = [ + '/pinboard/', + '/insights/pinboard/', + '/schedules/', + '/embed/viz/', + '/embed/insights/viz/', + '/liveboard/', + '/insights/liveboard/', + '/tsl-editor/PINBOARD_ANSWER_BOOK/', + '/import-tsl/PINBOARD_ANSWER_BOOK/', + ]; + if (liveboardRelatedRoutes.some((path) => data.data.currentPath.startsWith(path))) { + // Ignore the height reset of the frame, if the navigation is + // only within the liveboard page. + return; + } + this.setIFrameHeight(frameHeight || this.defaultHeight); + }; + if (this.viewConfig.fullHeight === true) { + this.on(exports.EmbedEvent.RouteChange, this.setIframeHeightForNonEmbedLiveboard); + this.on(exports.EmbedEvent.EmbedHeight, this.updateIFrameHeight); + this.on(exports.EmbedEvent.EmbedIframeCenter, this.embedIframeCenter); + this.on(exports.EmbedEvent.RequestVisibleEmbedCoordinates, this.requestVisibleEmbedCoordinatesHandler); + } + } + /** + * Extends the default APP_INIT payload with `embedParams.spotterSidebarConfig` + * so the conv-assist app can read sidebar configuration on initialisation. + * + * Precedence for `enablePastConversationsSidebar`: + * `spotterSidebarConfig.enablePastConversationsSidebar` wins over the + * deprecated top-level `enablePastConversationsSidebar` flag; if the former + * is absent the latter is used as a fallback. + * + * An invalid `spotterDocumentationUrl` triggers a validation error and is + * excluded from the payload rather than forwarded to the app. + */ + async getAppInitData() { + const defaultAppInitData = await super.getAppInitData(); + const sidebarInitData = buildSpotterSidebarAppInitData(defaultAppInitData, this.viewConfig, this.handleError.bind(this)); + return buildSpotterVizAppInitData(sidebarInitData, this.viewConfig); + } + /** + * Constructs a map of parameters to be passed on to the + * embedded Liveboard or visualization. + */ + getEmbedParams() { + const { tag, hideTagFilterChips, hideObjects, liveboardV2, showPrimaryNavbar, disableProfileAndHelp, hideHamburger, hideObjectSearch, hideNotification, hideApplicationSwitcher, hideOrgSwitcher, enableSearchAssist, newConnectionsExperience, fullHeight, dataPanelV2 = true, hideLiveboardHeader = false, showLiveboardTitle = true, showLiveboardDescription = true, showMaskedFilterChip = false, isLiveboardMasterpiecesEnabled = false, newChartsLibrary, hideHomepageLeftNav = false, modularHomeExperience = false, isLiveboardHeaderSticky = true, enableAskSage, collapseSearchBarInitially = false, enable2ColumnLayout, enableCustomColumnGroups = false, dataPanelCustomGroupsAccordionInitialState = DataPanelCustomColumnGroupsAccordionState.EXPAND_ALL, collapseSearchBar = true, isLiveboardCompactHeaderEnabled = false, showLiveboardVerifiedBadge = true, showLiveboardReverifyBanner = true, hideIrrelevantChipsInLiveboardTabs = false, isEnhancedFilterInteractivityEnabled = false, homePageSearchBarMode, isUnifiedSearchExperienceEnabled = true, enablePendoHelp = true, discoveryExperience, coverAndFilterOptionInPDF = false, isLiveboardStylingAndGroupingEnabled, isPNGInScheduledEmailsEnabled = false, isLiveboardXLSXCSVDownloadEnabled = false, isGranularXLSXCSVSchedulesEnabled = false, isCentralizedLiveboardFilterUXEnabled = false, isLinkParametersEnabled, updatedSpotterChatPrompt, enableStopAnswerGenerationEmbed, spotterChatConfig, minimumHeight, isThisPeriodInDateFiltersEnabled, enableHomepageAnnouncement = false, isContinuousLiveboardPDFEnabled = false, enableLiveboardDataCache, } = this.viewConfig; + let params = {}; + params[Param.PrimaryNavHidden] = !showPrimaryNavbar; + params[Param.HideProfleAndHelp] = !!disableProfileAndHelp; + params[Param.HideApplicationSwitcher] = !!hideApplicationSwitcher; + params[Param.HideOrgSwitcher] = !!hideOrgSwitcher; + params[Param.HideLiveboardHeader] = hideLiveboardHeader; + params[Param.ShowLiveboardTitle] = showLiveboardTitle; + params[Param.ShowLiveboardDescription] = !!showLiveboardDescription; + params[Param.ShowMaskedFilterChip] = showMaskedFilterChip; + params[Param.IsLiveboardMasterpiecesEnabled] = isLiveboardMasterpiecesEnabled; + if (newChartsLibrary !== undefined) { + params[Param.EnableNewChartLibrary] = newChartsLibrary; + } + params[Param.LiveboardHeaderSticky] = isLiveboardHeaderSticky; + params[Param.IsFullAppEmbed] = true; + params[Param.LiveboardHeaderV2] = isLiveboardCompactHeaderEnabled; + params[Param.IsEnhancedFilterInteractivityEnabled] = isEnhancedFilterInteractivityEnabled; + params[Param.ShowLiveboardVerifiedBadge] = showLiveboardVerifiedBadge; + params[Param.ShowLiveboardReverifyBanner] = showLiveboardReverifyBanner; + params[Param.HideIrrelevantFiltersInTab] = hideIrrelevantChipsInLiveboardTabs; + if (isUnifiedSearchExperienceEnabled !== undefined) { + params[Param.IsUnifiedSearchExperienceEnabled] = isUnifiedSearchExperienceEnabled; + } + params[Param.CoverAndFilterOptionInPDF] = !!coverAndFilterOptionInPDF; + params = this.getBaseQueryParams(params); + if (!isUndefined(updatedSpotterChatPrompt)) { + params[Param.UpdatedSpotterChatPrompt] = !!updatedSpotterChatPrompt; + } + if (!isUndefined(enableStopAnswerGenerationEmbed)) { + params[Param.EnableStopAnswerGenerationEmbed] = !!enableStopAnswerGenerationEmbed; + } + // Handle spotterChatConfig params + if (spotterChatConfig) { + const { hideToolResponseCardBranding, toolResponseCardBrandingLabel, spotterFileUploadEnabled, spotterFileUploadFileTypes, } = spotterChatConfig; + setParamIfDefined(params, Param.HideToolResponseCardBranding, hideToolResponseCardBranding, true); + setParamIfDefined(params, Param.ToolResponseCardBrandingLabel, toolResponseCardBrandingLabel); + if (spotterFileUploadEnabled !== undefined) { + params[Param.SpotterFileUploadEnabled] = spotterFileUploadEnabled; + } + if (spotterFileUploadFileTypes !== undefined) { + params[Param.SpotterFileUploadFileTypes] = JSON.stringify(spotterFileUploadFileTypes); + } + } + if (hideObjectSearch) { + params[Param.HideObjectSearch] = !!hideObjectSearch; + } + if (hideHamburger) { + params[Param.HideHamburger] = !!hideHamburger; + } + if (hideNotification) { + params[Param.HideNotification] = !!hideNotification; + } + if (fullHeight === true) { + params[Param.fullHeight] = true; + if (this.viewConfig.lazyLoadingForFullHeight) { + params[Param.IsLazyLoadingForEmbedEnabled] = true; + if (isValidCssMargin(this.viewConfig.lazyLoadingMargin)) { + params[Param.RootMarginForLazyLoad] = this.viewConfig.lazyLoadingMargin; + } + } + } + if (tag) { + params[Param.Tag] = tag; + } + if (hideObjects && hideObjects.length) { + params[Param.HideObjects] = JSON.stringify(hideObjects); + } + if (liveboardV2 !== undefined) { + params[Param.LiveboardV2Enabled] = liveboardV2; + } + if (enableSearchAssist !== undefined) { + params[Param.EnableSearchAssist] = enableSearchAssist; + } + if (newConnectionsExperience !== undefined) { + params[Param.EnableConnectionNewExperience] = newConnectionsExperience; + } + if (enable2ColumnLayout !== undefined) { + params[Param.Enable2ColumnLayout] = enable2ColumnLayout; + } + if (enableAskSage) { + params[Param.enableAskSage] = enableAskSage; + } + if (homePageSearchBarMode) { + params[Param.HomePageSearchBarMode] = homePageSearchBarMode; + } + if (enablePendoHelp !== undefined) { + params[Param.EnablePendoHelp] = enablePendoHelp; + } + if (isLiveboardStylingAndGroupingEnabled !== undefined) { + params[Param.IsLiveboardStylingAndGroupingEnabled] = isLiveboardStylingAndGroupingEnabled; + } + if (isPNGInScheduledEmailsEnabled !== undefined) { + params[Param.isPNGInScheduledEmailsEnabled] = isPNGInScheduledEmailsEnabled; + } + if (isLiveboardXLSXCSVDownloadEnabled !== undefined) { + params[Param.isLiveboardXLSXCSVDownloadEnabled] = isLiveboardXLSXCSVDownloadEnabled; + } + if (isGranularXLSXCSVSchedulesEnabled !== undefined) { + params[Param.isGranularXLSXCSVSchedulesEnabled] = isGranularXLSXCSVSchedulesEnabled; + } + if (hideTagFilterChips !== undefined) { + params[Param.HideTagFilterChips] = hideTagFilterChips; + } + if (isLinkParametersEnabled !== undefined) { + params[Param.isLinkParametersEnabled] = isLinkParametersEnabled; + } + if (isCentralizedLiveboardFilterUXEnabled != undefined) { + params[Param.isCentralizedLiveboardFilterUXEnabled] = isCentralizedLiveboardFilterUXEnabled; + } + if (isThisPeriodInDateFiltersEnabled !== undefined) { + params[Param.IsThisPeriodInDateFiltersEnabled] = isThisPeriodInDateFiltersEnabled; + } + if (enableHomepageAnnouncement !== undefined) { + params[Param.EnableHomepageAnnouncement] = enableHomepageAnnouncement; + } + if (isContinuousLiveboardPDFEnabled !== undefined) { + params[Param.IsWYSIWYGLiveboardPDFEnabled] = isContinuousLiveboardPDFEnabled; + } + this.defaultHeight = minimumHeight || this.defaultHeight; + if (enableLiveboardDataCache !== undefined) { + params[Param.EnableLiveboardDataCache] = enableLiveboardDataCache; + } + params[Param.DataPanelV2Enabled] = dataPanelV2; + params[Param.HideHomepageLeftNav] = hideHomepageLeftNav; + params[Param.ModularHomeExperienceEnabled] = modularHomeExperience; + params[Param.CollapseSearchBarInitially] = collapseSearchBarInitially || collapseSearchBar; + params[Param.EnableCustomColumnGroups] = enableCustomColumnGroups; + if (dataPanelCustomGroupsAccordionInitialState + === DataPanelCustomColumnGroupsAccordionState.COLLAPSE_ALL + || dataPanelCustomGroupsAccordionInitialState + === DataPanelCustomColumnGroupsAccordionState.EXPAND_FIRST) { + params[Param.DataPanelCustomGroupsAccordionInitialState] = dataPanelCustomGroupsAccordionInitialState; + } + else { + params[Param.DataPanelCustomGroupsAccordionInitialState] = DataPanelCustomColumnGroupsAccordionState.EXPAND_ALL; + } + if (modularHomeExperience !== undefined) { + params[Param.ModularHomeExperienceEnabled] = modularHomeExperience; + } + // Set navigation to v2 by default to avoid problems like the app + // switcher (9-dot menu) not showing when v3 navigation is turned on + // at the cluster level. + // To use v3 navigation, we must manually set the discoveryExperience + // settings. + params[Param.NavigationVersion] = 'v2'; + // Set homePageVersion to v2 by default to reset the LD flag value + // for the homepageVersion. + params[Param.HomepageVersion] = 'v2'; + if (discoveryExperience) { + // primaryNavbarVersion v3 will enabled the new left navigation + if (discoveryExperience.primaryNavbarVersion === PrimaryNavbarVersion.Sliding) { + params[Param.NavigationVersion] = discoveryExperience.primaryNavbarVersion; + // Enable the modularHomeExperience when Sliding is enabled. + params[Param.ModularHomeExperienceEnabled] = true; + } + // homePage v2 will enable the modular home page + // and it will override the modularHomeExperience value + if (discoveryExperience.homePage === HomePage.Modular) { + params[Param.ModularHomeExperienceEnabled] = true; + } + // ModularWithStylingChanges (v3) introduces the styling changes + // to the Modular Homepage. + // v3 will be the base version of homePageVersion. + if (discoveryExperience.homePage === HomePage.ModularWithStylingChanges) { + params[Param.HomepageVersion] = HomePage.ModularWithStylingChanges; + } + // listPageVersion can be changed to v2 or v3 + if (discoveryExperience.listPageVersion !== undefined) { + params[Param.ListPageVersion] = discoveryExperience.listPageVersion; + } + if (discoveryExperience.homePage === HomePage.Focused) { + params[Param.HomepageVersion] = HomePage.Focused; + } + } + const queryParams = getQueryParamString(params, true); + return queryParams; + } + /** + * Constructs the URL of the ThoughtSpot app page to be rendered. + * @param pageId The ID of the page to be embedded. + */ + getIFrameSrc() { + const { pageId, path, modularHomeExperience } = this.viewConfig; + const pageRoute = this.formatPath(path) || this.getPageRoute(pageId, modularHomeExperience); + let url = `${this.getRootIframeSrc()}/${pageRoute}`; + const tsPostHashParams = this.getThoughtSpotPostUrlParams(); + url = `${url}${tsPostHashParams}`; + return url; + } + /** + * Gets the ThoughtSpot route of the page for a particular page ID. + * @param pageId The identifier for a page in the ThoughtSpot app. + * @param modularHomeExperience + */ + getPageRoute(pageId, modularHomeExperience = false) { + switch (pageId) { + case exports.Page.Search: + return 'answer'; + case exports.Page.Answers: + return modularHomeExperience ? 'home/answers' : 'answers'; + case exports.Page.Liveboards: + return modularHomeExperience ? 'home/liveboards' : 'pinboards'; + case exports.Page.Pinboards: + return modularHomeExperience ? 'home/liveboards' : 'pinboards'; + case exports.Page.Data: + return 'data/tables'; + case exports.Page.SpotIQ: + return modularHomeExperience ? 'home/spotiq-analysis' : 'insights/results'; + case exports.Page.Monitor: + return modularHomeExperience ? 'home/monitor-alerts' : 'insights/monitor-alerts'; + case exports.Page.Home: + default: + return 'home'; + } + } + /** + * Formats the path provided by the user. + * @param path The URL path. + * @returns The URL path that the embedded app understands. + */ + formatPath(path) { + if (!path) { + return null; + } + // remove leading slash + if (path.indexOf('/') === 0) { + return path.substring(1); + } + return path; + } + /** + * Navigate to particular page for app embed. eg:answers/pinboards/home + * This is used for embedding answers, pinboards, visualizations and full application + * only. + * @param path string | number The string, set to iframe src and navigate to new page + * eg: appEmbed.navigateToPage('pinboards') + * When used with `noReload` (default: true) this can also be a number + * like 1/-1 to go forward/back. + * @param noReload boolean Trigger the navigation without reloading the page + * @version SDK: 1.12.0 | ThoughtSpot: 8.4.0.cl, 8.4.1-sw + */ + navigateToPage(path, noReload = false) { + if (!this.iFrame) { + logger$3.log('Please call render before invoking this method'); + return; + } + if (noReload) { + this.trigger(exports.HostEvent.Navigate, path); + } + else { + if (typeof path !== 'string') { + logger$3.warn('Path can only by a string when triggered without noReload'); + return; + } + const iframeSrc = this.iFrame.src; + const embedPath = '#/embed'; + const currentPath = iframeSrc.includes(embedPath) ? embedPath : '#'; + this.iFrame.src = `${iframeSrc.split(currentPath)[0]}${currentPath}/${path.replace(/^\/?#?\//, '')}`; + } + } + /** + * Destroys the ThoughtSpot embed, and remove any nodes from the DOM. + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + destroy() { + super.destroy(); + this.unregisterLazyLoadEvents(); + } + postRender() { + this.registerLazyLoadEvents(); + } + registerLazyLoadEvents() { + if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) { + // TODO: Use passive: true, install modernizr to check for passive + window.addEventListener('resize', this.sendFullHeightLazyLoadData); + window.addEventListener('scroll', this.sendFullHeightLazyLoadData, true); + } + } + unregisterLazyLoadEvents() { + if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) { + window.removeEventListener('resize', this.sendFullHeightLazyLoadData); + window.removeEventListener('scroll', this.sendFullHeightLazyLoadData); + } + } + /** + * Renders the embedded application pages in the ThoughtSpot app. + * @param renderOptions An object containing the page ID + * to be embedded. + */ + async render() { + await super.render(); + const src = this.getIFrameSrc(); + await this.renderV1Embed(src); + this.postRender(); + return this; + } + }; + + const getPreviewQuery = ` +query GetEurekaVizSnapshots( + $vizId: String!, $liveboardId: String!) { + getEurekaVizSnapshot( + id: $vizId + reportBookId: $liveboardId + reportBookType: "PINBOARD_ANSWER_BOOK" + version: 9999999 + ) { + id + vizContent + snapshotType + createdMs + } + } +`; + /** + * + * @param thoughtSpotHost + * @param vizId + * @param liveboardId + */ + async function getPreview(thoughtSpotHost, vizId, liveboardId) { + return graphqlQuery({ + query: getPreviewQuery, + variables: { vizId, liveboardId }, + thoughtSpotHost, + }); + } + + const addPreviewStylesIfNotPresent = () => { + const styleEl = document.getElementById('ts-preview-style'); + if (styleEl) { + return; + } + const previewStyles = ` + + + `; + document.head.insertAdjacentHTML('beforeend', previewStyles); + }; + + /** + * Copyright (c) 2022 + * + * Embed a ThoughtSpot Liveboard or visualization + * https://developers.thoughtspot.com/docs/embed-liveboard + * https://developers.thoughtspot.com/docs/embed-a-viz + * @summary Liveboard & visualization embed + * @author Ayon Ghosh + */ + /** + * Embed a ThoughtSpot Liveboard or visualization. When rendered it already + * waits for the authentication to complete, so you need not wait for + * `AuthStatus.SUCCESS`. + * @group Embed components + * @example + * ```js + * import { .. } from '@thoughtspot/visual-embed-sdk'; + * init({ ... }); + * const embed = new LiveboardEmbed("#container", { + * liveboardId: , + * // .. other params here. + * }) + * ``` + */ + let LiveboardEmbed$1 = class LiveboardEmbed extends V1Embed { + constructor(domSelector, viewConfig) { + viewConfig.embedComponentType = 'LiveboardEmbed'; + super(domSelector, viewConfig); + this.defaultHeight = 500; + this.sendFullHeightLazyLoadData = () => { + const data = calculateVisibleElementData(this.iFrame); + // this should be fired only if the lazyLoadingForFullHeight and fullHeight are true + if (this.viewConfig.lazyLoadingForFullHeight && this.viewConfig.fullHeight) { + this.trigger(exports.HostEvent.VisibleEmbedCoordinates, data); + } + }; + /** + * This is a handler for the RequestVisibleEmbedCoordinates event. + * It is used to send the visible coordinates data to the host application. + * @param data The event payload + * @param responder The responder function + */ + this.requestVisibleEmbedCoordinatesHandler = (data, responder) => { + logger$3.info('Sending RequestVisibleEmbedCoordinates', data); + const visibleCoordinatesData = calculateVisibleElementData(this.iFrame); + responder({ type: exports.EmbedEvent.RequestVisibleEmbedCoordinates, data: visibleCoordinatesData }); + }; + /** + * Set the iframe height as per the computed height received + * from the ThoughtSpot app. + * @param data The event payload + */ + this.updateIFrameHeight = (data) => { + this.setIFrameHeight(Math.max(data.data, this.defaultHeight)); + this.sendFullHeightLazyLoadData(); + }; + this.embedIframeCenter = (data, responder) => { + const obj = this.getIframeCenter(); + responder({ type: exports.EmbedEvent.EmbedIframeCenter, data: obj }); + }; + this.setIframeHeightForNonEmbedLiveboard = (data) => { + const { height: frameHeight } = this.viewConfig.frameParams || {}; + const liveboardRelatedRoutes = [ + '/pinboard/', + '/insights/pinboard/', + '/schedules/', + '/embed/viz/', + '/embed/insights/viz/', + '/liveboard/', + '/insights/liveboard/', + '/tsl-editor/PINBOARD_ANSWER_BOOK/', + '/import-tsl/PINBOARD_ANSWER_BOOK/', + ]; + if (liveboardRelatedRoutes.some((path) => data.data.currentPath.startsWith(path))) { + // Ignore the height reset of the frame, if the navigation is + // only within the liveboard page. + return; + } + this.setIFrameHeight(frameHeight || this.defaultHeight); + }; + /** + * @hidden + * Internal state to track the current liveboard id. + * This is used to navigate to the correct liveboard when the prerender is visible. + */ + this.currentLiveboardState = { + liveboardId: this.viewConfig.liveboardId, + vizId: this.viewConfig.vizId, + activeTabId: this.viewConfig.activeTabId, + personalizedViewId: this.viewConfig.personalizedViewId, + }; + if (this.viewConfig.fullHeight === true) { + if (this.viewConfig.vizId) { + logger$3.warn('Full height is currently only supported for Liveboard embeds.' + + 'Using full height with vizId might lead to unexpected behavior.'); + } + this.on(exports.EmbedEvent.RouteChange, this.setIframeHeightForNonEmbedLiveboard); + this.on(exports.EmbedEvent.EmbedHeight, this.updateIFrameHeight); + this.on(exports.EmbedEvent.EmbedIframeCenter, this.embedIframeCenter); + this.on(exports.EmbedEvent.RequestVisibleEmbedCoordinates, this.requestVisibleEmbedCoordinatesHandler); + } + } + async getAppInitData() { + const defaultAppInitData = await super.getAppInitData(); + return buildSpotterVizAppInitData(defaultAppInitData, this.viewConfig); + } + /** + * Construct a map of params to be passed on to the + * embedded Liveboard or visualization. + */ + getEmbedParams() { + const params = this.getEmbedParamsObject(); + const queryParams = getQueryParamString(params, true); + return queryParams; + } + getEmbedParamsObject() { + let params = {}; + params = this.getBaseQueryParams(params); + const { enableVizTransformations, fullHeight, defaultHeight, minimumHeight, visibleVizs, liveboardV2, vizId, hideTabPanel, activeTabId, hideLiveboardHeader, showLiveboardDescription, showLiveboardTitle, isLiveboardHeaderSticky = true, isLiveboardCompactHeaderEnabled = false, showLiveboardVerifiedBadge = true, showLiveboardReverifyBanner = true, hideIrrelevantChipsInLiveboardTabs = false, showMaskedFilterChip = false, isLiveboardMasterpiecesEnabled = false, newChartsLibrary, isEnhancedFilterInteractivityEnabled = false, enableAskSage, enable2ColumnLayout, dataPanelV2 = true, enableCustomColumnGroups = false, oAuthPollingInterval, isForceRedirect, dataSourceId, coverAndFilterOptionInPDF = false, isLiveboardStylingAndGroupingEnabled, isPNGInScheduledEmailsEnabled = false, isLiveboardXLSXCSVDownloadEnabled = false, isGranularXLSXCSVSchedulesEnabled = false, showSpotterLimitations, isCentralizedLiveboardFilterUXEnabled = false, isLinkParametersEnabled, updatedSpotterChatPrompt, enableStopAnswerGenerationEmbed, spotterChatConfig, isThisPeriodInDateFiltersEnabled, isContinuousLiveboardPDFEnabled = false, enableLiveboardDataCache, } = this.viewConfig; + const preventLiveboardFilterRemoval = this.viewConfig.preventLiveboardFilterRemoval + || this.viewConfig.preventPinboardFilterRemoval; + if (fullHeight === true) { + params[Param.fullHeight] = true; + if (this.viewConfig.lazyLoadingForFullHeight) { + params[Param.IsLazyLoadingForEmbedEnabled] = true; + if (isValidCssMargin(this.viewConfig.lazyLoadingMargin)) { + params[Param.RootMarginForLazyLoad] = this.viewConfig.lazyLoadingMargin; + } + } + } + this.defaultHeight = minimumHeight || defaultHeight || this.defaultHeight; + if (enableVizTransformations !== undefined) { + params[Param.EnableVizTransformations] = enableVizTransformations.toString(); + } + if (preventLiveboardFilterRemoval) { + params[Param.preventLiveboardFilterRemoval] = true; + } + if (!isUndefined(updatedSpotterChatPrompt)) { + params[Param.UpdatedSpotterChatPrompt] = !!updatedSpotterChatPrompt; + } + if (!isUndefined(enableStopAnswerGenerationEmbed)) { + params[Param.EnableStopAnswerGenerationEmbed] = !!enableStopAnswerGenerationEmbed; + } + if (visibleVizs) { + params[Param.visibleVizs] = visibleVizs; + } + params[Param.livedBoardEmbed] = true; + if (vizId) { + params[Param.vizEmbed] = true; + } + if (liveboardV2 !== undefined) { + params[Param.LiveboardV2Enabled] = liveboardV2; + } + if (enable2ColumnLayout !== undefined) { + params[Param.Enable2ColumnLayout] = enable2ColumnLayout; + } + if (hideTabPanel) { + params[Param.HideTabPanel] = hideTabPanel; + } + if (hideLiveboardHeader) { + params[Param.HideLiveboardHeader] = hideLiveboardHeader; + } + if (showLiveboardDescription) { + params[Param.ShowLiveboardDescription] = showLiveboardDescription; + } + if (showLiveboardTitle) { + params[Param.ShowLiveboardTitle] = showLiveboardTitle; + } + if (enableAskSage) { + params[Param.enableAskSage] = enableAskSage; + } + if (oAuthPollingInterval !== undefined) { + params[Param.OauthPollingInterval] = oAuthPollingInterval; + } + if (isForceRedirect) { + params[Param.IsForceRedirect] = isForceRedirect; + } + if (dataSourceId !== undefined) { + params[Param.DataSourceId] = dataSourceId; + } + if (isLiveboardStylingAndGroupingEnabled !== undefined) { + params[Param.IsLiveboardStylingAndGroupingEnabled] = isLiveboardStylingAndGroupingEnabled; + } + if (isPNGInScheduledEmailsEnabled !== undefined) { + params[Param.isPNGInScheduledEmailsEnabled] = isPNGInScheduledEmailsEnabled; + } + if (isLiveboardXLSXCSVDownloadEnabled !== undefined) { + params[Param.isLiveboardXLSXCSVDownloadEnabled] = isLiveboardXLSXCSVDownloadEnabled; + } + if (isGranularXLSXCSVSchedulesEnabled !== undefined) { + params[Param.isGranularXLSXCSVSchedulesEnabled] = isGranularXLSXCSVSchedulesEnabled; + } + if (showSpotterLimitations !== undefined) { + params[Param.ShowSpotterLimitations] = showSpotterLimitations; + } + // Handle spotterChatConfig params + if (spotterChatConfig) { + const { hideToolResponseCardBranding, toolResponseCardBrandingLabel, spotterFileUploadEnabled, spotterFileUploadFileTypes, } = spotterChatConfig; + setParamIfDefined(params, Param.HideToolResponseCardBranding, hideToolResponseCardBranding, true); + setParamIfDefined(params, Param.ToolResponseCardBrandingLabel, toolResponseCardBrandingLabel); + if (spotterFileUploadEnabled !== undefined) { + params[Param.SpotterFileUploadEnabled] = spotterFileUploadEnabled; + } + if (spotterFileUploadFileTypes !== undefined) { + params[Param.SpotterFileUploadFileTypes] = JSON.stringify(spotterFileUploadFileTypes); + } + } + if (isLinkParametersEnabled !== undefined) { + params[Param.isLinkParametersEnabled] = isLinkParametersEnabled; + } + if (isCentralizedLiveboardFilterUXEnabled !== undefined) { + params[Param.isCentralizedLiveboardFilterUXEnabled] = isCentralizedLiveboardFilterUXEnabled; + } + if (isThisPeriodInDateFiltersEnabled !== undefined) { + params[Param.IsThisPeriodInDateFiltersEnabled] = isThisPeriodInDateFiltersEnabled; + } + if (isContinuousLiveboardPDFEnabled !== undefined) { + params[Param.IsWYSIWYGLiveboardPDFEnabled] = isContinuousLiveboardPDFEnabled; + } + if (enableLiveboardDataCache !== undefined) { + params[Param.EnableLiveboardDataCache] = enableLiveboardDataCache; + } + if (newChartsLibrary !== undefined) { + params[Param.EnableNewChartLibrary] = newChartsLibrary; + } + params[Param.LiveboardHeaderSticky] = isLiveboardHeaderSticky; + params[Param.LiveboardHeaderV2] = isLiveboardCompactHeaderEnabled; + params[Param.ShowLiveboardVerifiedBadge] = showLiveboardVerifiedBadge; + params[Param.ShowLiveboardReverifyBanner] = showLiveboardReverifyBanner; + params[Param.HideIrrelevantFiltersInTab] = hideIrrelevantChipsInLiveboardTabs; + params[Param.ShowMaskedFilterChip] = showMaskedFilterChip; + params[Param.IsLiveboardMasterpiecesEnabled] = isLiveboardMasterpiecesEnabled; + params[Param.IsEnhancedFilterInteractivityEnabled] = isEnhancedFilterInteractivityEnabled; + params[Param.DataPanelV2Enabled] = dataPanelV2; + params[Param.EnableCustomColumnGroups] = enableCustomColumnGroups; + params[Param.CoverAndFilterOptionInPDF] = coverAndFilterOptionInPDF; + getQueryParamString(params, true); + return params; + } + getIframeSuffixSrc(liveboardId, vizId, activeTabId, personalizedViewId) { + // Extract view from liveboardId if passed along with it (legacy + // approach) + // View must be appended as query param at the end, not + // embedded in path + let liveboardGuid = liveboardId; + let legacyViewId; + if (liveboardId?.includes('?')) { + const [id, query] = liveboardId.split('?'); + liveboardGuid = id; + const params = new URLSearchParams(query); + legacyViewId = params.get('view') || undefined; + } + // personalizedViewId takes precedence over legacyViewId (when passed + // as part of liveboardId) + const effectiveViewId = personalizedViewId || legacyViewId; + let suffix = `/embed/viz/${liveboardGuid}`; + if (activeTabId) { + suffix = `${suffix}/tab/${activeTabId}`; + } + if (vizId) { + suffix = `${suffix}/${vizId}`; + } + const additionalParams = {}; + if (effectiveViewId) { + additionalParams.view = effectiveViewId; + } + const tsPostHashParams = this.getThoughtSpotPostUrlParams(additionalParams); + suffix = `${suffix}${tsPostHashParams}`; + return suffix; + } + /** + * Construct the URL of the embedded ThoughtSpot Liveboard or visualization + * to be loaded within the iFrame. + */ + getIFrameSrc() { + const { vizId, activeTabId, personalizedViewId } = this.viewConfig; + const liveboardId = this.viewConfig.liveboardId ?? this.viewConfig.pinboardId; + if (!liveboardId) { + this.handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.LIVEBOARD_VIZ_ID_VALIDATION, + code: EmbedErrorCodes.LIVEBOARD_ID_MISSING, + error: ERROR_MESSAGE.LIVEBOARD_VIZ_ID_VALIDATION, + }); + } + return `${this.getRootIframeSrc()}${this.getIframeSuffixSrc(liveboardId, vizId, activeTabId, personalizedViewId)}`; + } + setActiveTab(data) { + if (!this.viewConfig.vizId) { + const prefixPath = this.iFrame.src.split('#/')[1].split('/tab')[0]; + const path = `${prefixPath}/tab/${data.tabId}`; + super.trigger(exports.HostEvent.Navigate, path); + } + } + async showPreviewLoader() { + if (!this.viewConfig.showPreviewLoader || !this.viewConfig.vizId) { + return; + } + try { + const preview = await getPreview(this.thoughtSpotHost, this.viewConfig.vizId, this.viewConfig.liveboardId); + if (!preview.vizContent) { + return; + } + addPreviewStylesIfNotPresent(); + const div = document.createElement('div'); + div.innerHTML = ` +
+ ${preview.vizContent} +
+ `; + const previewDiv = div.firstElementChild; + this.hostElement.appendChild(previewDiv); + this.hostElement.style.position = 'relative'; + this.on(exports.EmbedEvent.Data, () => { + previewDiv.remove(); + }); + } + catch (error) { + console.error('Error fetching preview', error); + } + } + beforePrerenderVisible() { + super.beforePrerenderVisible(); + const embedObj = this.getPreRenderObj(); + this.executeAfterEmbedContainerLoaded(() => { + this.navigateToLiveboard(this.viewConfig.liveboardId, this.viewConfig.vizId, this.viewConfig.activeTabId, this.viewConfig.personalizedViewId); + if (embedObj) { + embedObj.currentLiveboardState = { + liveboardId: this.viewConfig.liveboardId, + vizId: this.viewConfig.vizId, + activeTabId: this.viewConfig.activeTabId, + personalizedViewId: this.viewConfig.personalizedViewId, + }; + } + }); + } + async handleRenderForPrerender() { + if (isUndefined(this.viewConfig.liveboardId)) { + return this.prerenderGeneric(); + } + return super.handleRenderForPrerender(); + } + /** + * Triggers an event to the embedded app + * @param {HostEvent} messageType The event type + * @param {any} data The payload to send with the message + * @returns A promise that resolves with the response from the embedded app + */ + trigger(messageType, data = {}, context) { + const dataWithVizId = data; + if (messageType === exports.HostEvent.SetActiveTab) { + this.setActiveTab(data); + return Promise.resolve(null); + } + if (typeof dataWithVizId === 'object' && this.viewConfig.vizId) { + dataWithVizId.vizId = this.viewConfig.vizId; + } + return super.trigger(messageType, dataWithVizId, context); + } + /** + * Destroys the ThoughtSpot embed, and remove any nodes from the DOM. + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + destroy() { + super.destroy(); + this.unregisterLazyLoadEvents(); + } + postRender() { + this.registerLazyLoadEvents(); + } + registerLazyLoadEvents() { + if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) { + // TODO: Use passive: true, install modernizr to check for passive + window.addEventListener('resize', this.sendFullHeightLazyLoadData); + window.addEventListener('scroll', this.sendFullHeightLazyLoadData, true); + } + } + unregisterLazyLoadEvents() { + if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) { + window.removeEventListener('resize', this.sendFullHeightLazyLoadData); + window.removeEventListener('scroll', this.sendFullHeightLazyLoadData); + } + } + /** + * Render an embedded ThoughtSpot Liveboard or visualization + * @param renderOptions An object specifying the Liveboard ID, + * visualization ID and the runtime filters. + */ + async render() { + await super.render(); + const src = this.getIFrameSrc(); + await this.renderV1Embed(src); + this.showPreviewLoader(); + this.postRender(); + return this; + } + navigateToLiveboard(liveboardId, vizId, activeTabId, personalizedViewId) { + const path = this.getIframeSuffixSrc(liveboardId, vizId, activeTabId, personalizedViewId); + this.viewConfig.liveboardId = liveboardId; + this.viewConfig.activeTabId = activeTabId; + this.viewConfig.vizId = vizId; + this.viewConfig.personalizedViewId = personalizedViewId; + if (this.isRendered) { + this.trigger(exports.HostEvent.Navigate, path.substring(1)); + } + else if (this.viewConfig.preRenderId) { + this.preRender(true); + } + else { + this.render(); + } + } + /** + * Returns the full url of the Liveboard/visualization which can be used to open + * this Liveboard inside the full ThoughtSpot application in a new tab. + * @returns url string + */ + getLiveboardUrl() { + let url = `${this.thoughtSpotHost}/#/pinboard/${this.viewConfig.liveboardId}`; + if (this.viewConfig.activeTabId) { + url = `${url}/tab/${this.viewConfig.activeTabId}`; + } + if (this.viewConfig.vizId) { + url = `${url}/${this.viewConfig.vizId}`; + } + if (this.viewConfig.personalizedViewId) { + url = `${url}?view=${this.viewConfig.personalizedViewId}`; + } + return url; + } + }; + + const createConversation = ` +mutation CreateConversation($params: Input_convassist_CreateConversationRequest) { + ConvAssist__createConversation(request: $params) { + convId + initialCtx { + type + tsAnsCtx { + sessionId + genNo + stateKey { + transactionId + generationNumber + } + worksheet { + worksheetId + worksheetName + } + } + } + } +} +`; + const sendMessage = ` +query SendMessage($params: Input_convassist_SendMessageRequest) { + ConvAssist__sendMessage(request: $params) { + responses { + timestamp + msgId + data { + asstRespData { + tool + asstRespText + nlsAnsData { + sageQuerySuggestions { + sageQueryTokens { + additions { + phrase { + isCompletePhrase + numTokens + phraseType + startIndex + __typename + } + tokens { + token + dataType + typeEnum + guid + tokenMetadata { + name + __typename + } + __typename + } + __typename + } + phrases { + isCompletePhrase + numTokens + phraseType + startIndex + __typename + } + removals { + phrase { + isCompletePhrase + numTokens + phraseType + startIndex + __typename + } + tokens { + token + dataType + typeEnum + guid + tokenMetadata { + name + __typename + } + __typename + } + __typename + } + tokens { + token + dataType + typeEnum + guid + tokenMetadata { + name + __typename + } + __typename + } + __typename + } + llmReasoning { + assumptions + clarifications + interpretation + __typename + } + tokens + tmlTokens + worksheetId + tokens + description + title + tmlTokens + cached + sqlQuery + sessionId + genNo + formulaInfo { + name + expression + __typename + } + tmlPhrases + ambiguousPhrases { + alternativePhrases { + phraseType + token { + token + dataType + typeEnum + guid + tokenMetadata { + name + __typename + } + __typename + } + __typename + } + ambiguityType + token { + token + dataType + typeEnum + guid + tokenMetadata { + name + __typename + } + __typename + } + __typename + } + ambiguousTokens { + alternativeTokens { + token + dataType + typeEnum + guid + tokenMetadata { + name + deprecatedTableGuid + deprecatedTableName + isFormula + rootTables { + created + description + guid + indexVersion + modified + name + __typename + } + schemaTableUserDefinedName + table { + created + description + guid + indexVersion + modified + name + __typename + } + __typename + } + __typename + } + ambiguityType + token { + token + dataType + typeEnum + guid + tokenMetadata { + name + __typename + } + __typename + } + __typename + } + stateKey { + transactionId + generationNumber + transactionId + __typename + } + subQueries { + tokens + cohortConfig { + anchorColumnId + cohortAnswerGuid + cohortGroupingType + cohortGuid + cohortType + combineNonGroupValues + description + groupExcludedQueryValues + hideExcludedQueryValues + isEditable + name + nullOutputValue + returnColumnId + __typename + } + formulas { + name + expression + __typename + } + __typename + } + visualizationSuggestion { + chartType + displayMode + axisConfigs { + category + color + hidden + size + sort + x + y + __typename + } + usersVizIntentApplied + customChartConfigs { + dimensions { + columns + key + __typename + } + key + __typename + } + customChartGuid + __typename + } + tableData { + columnDataLite { + columnId + columnDataType + dataValue + columnName + __typename + } + __typename + } + warningType + cached + __typename + } + debugInfo { + fewShotExamples { + chartType + id + mappingId + nlQuery + nlQueryConcepts + sageQuery + scope + sql + tml + __typename + } + __typename + } + __typename + } + __typename + } + errorData { + tool + errCode + errTxt + toolErrCode + __typename + } + __typename + } + type + __typename + } + prevCtx { + genNo + sessionId + __typename + } + __typename + } +} +`; + + class Conversation { + constructor(thoughtSpotHost, worksheetId) { + this.thoughtSpotHost = thoughtSpotHost; + this.worksheetId = worksheetId; + this.inProgress = null; + this.inProgress = this.init(); + } + async init() { + const { convId } = await this.createConversation(); + this.conversationId = convId; + } + createConversation() { + return this.executeQuery(createConversation, { + params: { + initialCtx: { + tsWorksheetCtx: { + worksheet: { + worksheetId: this.worksheetId, + }, + }, + type: 'TS_WORKSHEET', + }, + userInfo: { + tenantUrl: `${this.thoughtSpotHost}/prism`, + }, + }, + }); + } + async sendMessage(userMessage) { + await this.inProgress; + try { + const { responses } = await this.executeQuery(sendMessage, { + params: { + convId: this.conversationId, + headers: [], + msg: { + data: { + userCmdData: { + cmdText: userMessage, + nlsData: { + worksheetId: this.worksheetId, + questionType: 'ANSWER_SPEC_GENERATION', + }, + }, + }, + msgId: crypto.randomUUID(), + type: 'USER_COMMAND', + }, + }, + }); + const data = responses[0].data; + return { + convId: this.conversationId, + messageId: responses[0].msgId, + data: { + ...data.asstRespData.nlsAnsData.sageQuerySuggestions[0], + convId: this.conversationId, + messageId: responses[0].msgId, + }, + error: null, + }; + } + catch (error) { + return { error }; + } + } + async executeQuery(query, variables) { + return graphqlQuery({ + query, + variables, + thoughtSpotHost: this.thoughtSpotHost, + isCompositeQuery: false, + }); + } + } + + let ConversationMessage$1 = class ConversationMessage extends TsEmbed { + constructor(container, viewConfig) { + viewConfig.embedComponentType = 'bodyless-conversation'; + super(container, viewConfig); + this.viewConfig = viewConfig; + } + getEmbedParamsObject() { + const queryParams = this.getBaseQueryParams(); + queryParams[Param.HideActions] = [...(queryParams[Param.HideActions] ?? [])]; + queryParams[Param.isSpotterAgentEmbed] = true; + return queryParams; + } + getIframeSrc() { + const { sessionId, genNo, acSessionId, acGenNo, convId, messageId, } = this.viewConfig; + const path = 'conv-assist-answer'; + const queryParams = this.getEmbedParamsObject(); + let query = ''; + const queryParamsString = getQueryParamString(queryParams, true); + if (queryParamsString) { + query = `?${queryParamsString}`; + } + const tsPostHashParams = this.getThoughtSpotPostUrlParams({ + sessionId, + genNo, + acSessionId, + acGenNo, + convId, + messageId, + }); + return `${this.getEmbedBasePath(query)}/embed/${path}${tsPostHashParams}`; + } + async render() { + await super.render(); + const src = this.getIframeSrc(); + await this.renderIFrame(src); + return this; + } + }; + /** + * Create a conversation embed, which can be integrated inside + * chatbots or other conversational interfaces. + * @version SDK: 1.37.0 | ThoughtSpot: 10.9.0.cl + * @group Embed components + * @example + * ```js + * import { SpotterAgentEmbed } from '@thoughtspot/visual-embed-sdk'; + * + * const conversation = new SpotterAgentEmbed({ + * worksheetId: 'worksheetId', + * }); + * + * const { container, error } = await conversation.sendMessage('show me sales by region'); + * + * // append the container to the DOM + * document.body.appendChild(container); // or to any other element + * ``` + */ + class SpotterAgentEmbed { + constructor(viewConfig) { + this.viewConfig = viewConfig; + const embedConfig = getEmbedConfig(); + this.conversationService = new Conversation(embedConfig.thoughtSpotHost, viewConfig.worksheetId); + } + async sendMessage(userMessage) { + const { data, error } = await this.conversationService.sendMessage(userMessage); + if (error) { + return { error }; + } + const container = document.createElement('div'); + const embed = new ConversationMessage$1(container, { + ...this.viewConfig, + convId: data.convId, + messageId: data.messageId, + sessionId: data.sessionId, + genNo: data.genNo, + acSessionId: data.stateKey.transactionId, + acGenNo: data.stateKey.generationNumber, + }); + await embed.render(); + return { container, viz: embed }; + } + /** + * Send a message to the conversation service and return only the data. + * @param userMessage - The message to send to the conversation service. + * @returns The data from the conversation service. + */ + async sendMessageData(userMessage) { + try { + const { data, error } = await this.conversationService.sendMessage(userMessage); + if (error) { + return { error }; + } + return { data: { + convId: data.convId, + messageId: data.messageId, + sessionId: data.sessionId, + genNo: data.genNo, + acSessionId: data.stateKey.transactionId, + acGenNo: data.stateKey.generationNumber, + } }; + } + catch (error) { + return { error: error }; + } + } + } + + /** + * + * @param props + */ + function getViewPropsAndListeners(props) { + return Object.keys(props).reduce((accu, key) => { + if (key.startsWith('on')) { + const eventName = key.substr(2); + accu.listeners[exports.EmbedEvent[eventName]] = props[key]; + } + else { + accu.viewConfig[key] = props[key]; + } + return accu; + }, { + viewConfig: {}, + listeners: {}, + }); + } + + /** + * Embed ThoughtSpot AI Conversation. + * @version SDK: 1.37.0 | ThoughtSpot: 10.9.0.cl + * @group Embed components + * @example + * ```js + * const conversation = new SpotterEmbed('#tsEmbed', { + * worksheetId: 'worksheetId', + * searchOptions: { + * searchQuery: 'searchQuery', + * }, + * }); + * conversation.render(); + * ``` + */ + let SpotterEmbed$1 = class SpotterEmbed extends TsEmbed { + constructor(container, viewConfig) { + viewConfig = { + embedComponentType: 'conversation', + excludeRuntimeFiltersfromURL: true, + excludeRuntimeParametersfromURL: true, + ...viewConfig, + }; + super(container, viewConfig); + this.viewConfig = viewConfig; + } + /** + * Extends the default APP_INIT payload with `embedParams.spotterSidebarConfig` + * so the conv-assist app can read sidebar configuration on initialisation. + * + * Precedence for `enablePastConversationsSidebar`: + * `spotterSidebarConfig.enablePastConversationsSidebar` wins over the + * deprecated top-level `enablePastConversationsSidebar` flag; if the former + * is absent the latter is used as a fallback. + * + * An invalid `spotterDocumentationUrl` triggers a validation error and is + * excluded from the payload rather than forwarded to the app. + */ + async getAppInitData() { + const defaultAppInitData = await super.getAppInitData(); + return buildSpotterSidebarAppInitData(defaultAppInitData, this.viewConfig, this.handleError.bind(this)); + } + getEmbedParamsObject() { + const { worksheetId, searchOptions, disableSourceSelection, hideSourceSelection, dataPanelV2, showSpotterLimitations, hideSampleQuestions, runtimeFilters, excludeRuntimeFiltersfromURL, runtimeParameters, excludeRuntimeParametersfromURL, updatedSpotterChatPrompt, enableStopAnswerGenerationEmbed, spotterChatConfig, } = this.viewConfig; + if (!worksheetId) { + this.handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.SPOTTER_EMBED_WORKSHEED_ID_NOT_FOUND, + code: EmbedErrorCodes.WORKSHEET_ID_NOT_FOUND, + error: ERROR_MESSAGE.SPOTTER_EMBED_WORKSHEED_ID_NOT_FOUND, + }); + } + const queryParams = this.getBaseQueryParams(); + queryParams[Param.SpotterEnabled] = true; + // Boolean params + setParamIfDefined(queryParams, Param.DisableSourceSelection, disableSourceSelection, true); + setParamIfDefined(queryParams, Param.HideSourceSelection, hideSourceSelection, true); + setParamIfDefined(queryParams, Param.DataPanelV2Enabled, dataPanelV2, true); + setParamIfDefined(queryParams, Param.ShowSpotterLimitations, showSpotterLimitations, true); + setParamIfDefined(queryParams, Param.HideSampleQuestions, hideSampleQuestions, true); + setParamIfDefined(queryParams, Param.UpdatedSpotterChatPrompt, updatedSpotterChatPrompt, true); + setParamIfDefined(queryParams, Param.EnableStopAnswerGenerationEmbed, enableStopAnswerGenerationEmbed, true); + // Handle spotterChatConfig params + if (spotterChatConfig) { + const { hideToolResponseCardBranding, toolResponseCardBrandingLabel, spotterFileUploadEnabled, spotterFileUploadFileTypes, } = spotterChatConfig; + setParamIfDefined(queryParams, Param.HideToolResponseCardBranding, hideToolResponseCardBranding, true); + setParamIfDefined(queryParams, Param.ToolResponseCardBrandingLabel, toolResponseCardBrandingLabel); + setParamIfDefined(queryParams, Param.SpotterFileUploadEnabled, spotterFileUploadEnabled, true); + if (spotterFileUploadFileTypes !== undefined) { + queryParams[Param.SpotterFileUploadFileTypes] = JSON.stringify(spotterFileUploadFileTypes); + } + } + return queryParams; + } + getIframeSrc() { + const { worksheetId, searchOptions, runtimeFilters, excludeRuntimeFiltersfromURL, runtimeParameters, excludeRuntimeParametersfromURL, } = this.viewConfig; + const path = 'insights/conv-assist'; + const queryParams = this.getEmbedParamsObject(); + let query = ''; + const queryParamsString = getQueryParamString(queryParams, true); + if (queryParamsString) { + query = `?${queryParamsString}`; + } + const filterQuery = getFilterQuery(runtimeFilters || []); + if (filterQuery && !excludeRuntimeFiltersfromURL) { + query += `&${filterQuery}`; + } + const parameterQuery = getRuntimeParameters(runtimeParameters || []); + if (parameterQuery && !excludeRuntimeParametersfromURL) { + query += `&${parameterQuery}`; + } + const tsPostHashParams = this.getThoughtSpotPostUrlParams({ + worksheet: worksheetId, + query: searchOptions?.searchQuery || '', + }); + return `${this.getEmbedBasePath(query)}/embed/${path}${tsPostHashParams}`; + } + async render() { + await super.render(); + const src = this.getIframeSrc(); + await this.renderIFrame(src); + return this; + } + }; + /** + * Embed ThoughtSpot AI Conversation. + * Use {@link SpotterEmbed} instead + * @version SDK: 1.37.0 | ThoughtSpot: 10.9.0.cl + * @deprecated from SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + * @group Embed components + * @example + * ```js + * const conversation = new SpotterEmbed('#tsEmbed', { + * worksheetId: 'worksheetId', + * searchOptions: { + * searchQuery: 'searchQuery', + * }, + * }); + * conversation.render(); + * ``` + */ + let ConversationEmbed$1 = class ConversationEmbed extends SpotterEmbed$1 { + constructor(container, viewConfig) { + viewConfig = { + embedComponentType: 'conversation', + excludeRuntimeFiltersfromURL: true, + excludeRuntimeParametersfromURL: true, + ...viewConfig, + }; + super(container, viewConfig); + this.viewConfig = viewConfig; + } + }; + + const componentFactory = (EmbedConstructor, + // isPreRenderedComponent: Specifies whether the component being returned is + // intended for preRendering. If set to true, the component will call the + // Embed.preRender() method instead of the usual render method, and it will + // not be destroyed when the component is unmounted. + isPreRenderedComponent = false) => { + const Component = React.forwardRef((props, forwardedRef) => { + const ref = React.useRef(null); + const { className, style, ...embedProps } = props; + const { viewConfig, listeners } = getViewPropsAndListeners(embedProps); + const handleDestroy = (tsEmbed) => { + // do not destroy if it is a preRender component + if (isPreRenderedComponent) + return; + // if component is connected to a preRendered component + if (props.preRenderId) { + tsEmbed.hidePreRender(); + return; + } + tsEmbed.destroy(); + }; + const handlePreRenderRendering = (tsEmbed) => { + tsEmbed.preRender(); + }; + const handleDefaultRendering = (tsEmbed) => { + // if component is connected to a preRendered component + if (props.preRenderId) { + tsEmbed.showPreRender(); + return; + } + tsEmbed.render(); + }; + const handleRendering = (tsEmbed) => { + if (isPreRenderedComponent) { + handlePreRenderRendering(tsEmbed); + return; + } + handleDefaultRendering(tsEmbed); + }; + useDeepCompareEffect(() => { + const tsEmbed = new EmbedConstructor(ref.current, deepMerge({ + insertAsSibling: viewConfig.insertAsSibling, + frameParams: { + class: viewConfig.insertAsSibling ? className || '' : '', + }, + }, viewConfig)); + Object.keys(listeners).forEach((eventName) => { + tsEmbed.on(eventName, listeners[eventName]); + }); + handleRendering(tsEmbed); + if (forwardedRef) { + forwardedRef.current = tsEmbed; + } + return () => { + handleDestroy(tsEmbed); + }; + }, [viewConfig, listeners]); + const preRenderStyles = isPreRenderedComponent ? { display: 'none' } : {}; + // We dont add any component for preRenderedComponent + if (isPreRenderedComponent) + return React.createElement(React.Fragment, null); + return viewConfig.insertAsSibling ? (React.createElement("span", { "data-testid": "tsEmbed", ref: ref, style: { position: 'absolute', ...preRenderStyles } })) : (React.createElement("div", { "data-testid": "tsEmbed", ref: ref, style: { ...style, ...preRenderStyles }, className: `ts-embed-container ${className}` })); + }); + Component.displayName = EmbedConstructor.name || 'EmbedComponent'; + return Component; + }; + /** + * React component for Search Embed. + * @example + * ```tsx + * function Search() { + * return + * } + * ``` + */ + const SearchEmbed = componentFactory(SearchEmbed$1); + const PreRenderedSearchEmbed = componentFactory(SearchEmbed$1, true); + /** + * React component for Full app Embed. + * @example + * ```tsx + * function App() { + * return console.error(error)} + * /> + * } + * ``` + */ + const AppEmbed = componentFactory(AppEmbed$1); + /** + * React component for PreRendered App embed. + * + * PreRenderedAppEmbed will preRender the AppEmbed and will be hidden by + * default. + * + * AppEmbed with preRenderId passed will call showPreRender on the embed. + * @example + * ```tsx + * function LandingPageComponent() { + * return + * } + * ``` + * function MyComponent() { + * return + * } + * ``` + */ + const PreRenderedAppEmbed = componentFactory(AppEmbed$1, true); + /** + * React component for Liveboard embed. + * @example + * ```tsx + * function Liveboard() { + * return console.log('Liveboard rendered')} + * vizId="vizId" {/* if doing viz embed *\/} + * /> + * } + * ``` + */ + const LiveboardEmbed = componentFactory(LiveboardEmbed$1); + const PinboardEmbed = LiveboardEmbed; + /** + * React component for PreRendered Liveboard embed. + * + * PreRenderedLiveboardEmbed will preRender the liveboard and will be hidden by default. + * + * LiveboardEmbed with preRenderId passed will call showPreRender on the embed. + * + * If LiveboardEmbed is rendered before PreRenderedLiveboardEmbed is rendered it + * tries to preRender the LiveboardEmbed, so it is recommended to pass the + * liveboardId to both the components. + * @example + * ```tsx + * function LandingPageComponent() { + * return + * } + * ``` + * function MyComponent() { + * return + * } + * ``` + */ + const PreRenderedLiveboardEmbed = componentFactory(LiveboardEmbed$1, true); + const PreRenderedPinboardEmbed = PreRenderedLiveboardEmbed; + /** + * React component for Search bar embed. + * @example + * ```tsx + * function SearchBar() { + * return + * } + * ``` + */ + const SearchBarEmbed = componentFactory(SearchBarEmbed$1); + /** + * React component for PreRendered SearchBar embed. + * + * PreRenderedSearchBarEmbed will preRender the SearchBarEmbed and will be hidden by + * default. + * + * SearchBarEmbed with preRenderId passed will call showPreRender on the embed. + * @example + * ```tsx + * function LandingPageComponent() { + * return + * } + * ``` + * function MyComponent() { + * return + * } + * ``` + */ + const PreRenderedSearchBarEmbed = componentFactory(SearchBarEmbed$1, true); + /** + * React component for LLM based conversation BI. + * @example + * ```tsx + * function Sage() { + * return " + * }} + * ... other view config props or event listeners. + * /> + * } + * ``` + */ + const SpotterEmbed = componentFactory(SpotterEmbed$1); + /** + * React component for LLM based conversation BI. + * Use {@link SpotterEmbed} instead + * @deprecated from SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + * @example + * ```tsx + * function Sage() { + * return " + * }} + * ... other view config props or event listeners. + * /> + * } + * ``` + */ + const ConversationEmbed = componentFactory(ConversationEmbed$1); + const ConversationMessage = componentFactory(ConversationMessage$1); + /** + * React component for displaying individual conversation messages from SpotterAgent. + * + * This component renders a single message response from your ThoughtSpot conversation, + * showing charts, visualizations, or text responses based on the user's query. + * + * @version SDK: 1.39.0 | ThoughtSpot: 10.11.0.cl + * @example + * ```tsx + * const { sendMessage } = useSpotterAgent({ worksheetId: 'worksheetId' }); + * const result = await sendMessage('show me sales by region'); + * + * if (!result.error) { + * // Simple usage - just pass the message data + * + * + * // With optional query for context + * + * } + * ``` + */ + const SpotterMessage = React.forwardRef((props, ref) => { + const { message, query: _, ...otherProps } = props; + return (React.createElement(ConversationMessage, { ref: ref, ...message, ...otherProps })); + }); + SpotterMessage.displayName = 'SpotterMessage'; + /** + * React component for PreRendered Conversation embed. + * + * PreRenderedConversationEmbed will preRender the SpotterEmbed and will be hidden by + * default. + * + * SpotterEmbed with preRenderId passed will call showPreRender on the embed. + * @example + * ```tsx + * function LandingPageComponent() { + * return + * } + * ``` + * function MyComponent() { + * return + * } + * ``` + */ + const PreRenderedConversationEmbed = componentFactory(SpotterEmbed$1, true); + /** + * Get a reference to the embed component to trigger events on the component. + * @example + * ``` + * function Component() { + * const ref = useEmbedRef(); + * useEffect(() => { + * ref.current.trigger( + * EmbedEvent.UpdateRuntimeFilter, + * [{ columnName: 'name', operator: 'EQ', values: ['value']}]); + * }, []) + * return } /> + * } + * ``` + * @returns {React.MutableRefObject} ref + */ + function useEmbedRef() { + return React.useRef(null); + } + /** + * + * @version SDK: 1.36.2 | ThoughtSpot: * + * @param config - EmbedConfig + * @returns AuthEventEmitter + * @example + * ``` + * function Component() { + * const authEE = useInit({ ...initConfig }); + * return } /> + * } + * ``` + */ + function useInit(config) { + const ref = React.useRef(null); + useDeepCompareEffect(() => { + const authEE = init(config); + ref.current = authEE; + }, [config]); + return ref; + } + /** + * React hook for interacting with SpotterAgent AI conversations. + * + * This hook provides a sendMessage function that allows you to send natural language + * queries to your data and get back AI-generated responses with visualizations. + * + * @version SDK: 1.39.0 | ThoughtSpot: 10.11.0.cl + * @param config - Configuration object containing worksheetId and other options + * @returns Object with sendMessage function that returns conversation results + * @example + * ```tsx + * const { sendMessage } = useSpotterAgent({ worksheetId: 'worksheetId' }); + * + * const handleQuery = async () => { + * const result = await sendMessage('show me sales by region'); + * + * if (!result.error) { + * // Display the message response + * + * } else { + * console.error('Error:', result.error); + * } + * }; + * ``` + */ + function useSpotterAgent(config) { + const serviceRef = React.useRef(null); + useDeepCompareEffect(() => { + if (serviceRef.current) { + serviceRef.current = null; + } + serviceRef.current = new SpotterAgentEmbed(config); + return () => { + serviceRef.current = null; + }; + }, [config]); + const sendMessage = React.useCallback(async (query) => { + if (!serviceRef.current) { + return { error: new Error(ERROR_MESSAGE.SPOTTER_AGENT_NOT_INITIALIZED) }; + } + const result = await serviceRef.current.sendMessageData(query); + if (result.error) { + return { error: result.error }; + } + return { + query: query, + message: { + ...result.data, + worksheetId: config.worksheetId, + }, + }; + }, [config.worksheetId]); + return { + sendMessage, + }; + } + + const ALIAS = Symbol.for('yaml.alias'); + const DOC = Symbol.for('yaml.document'); + const MAP = Symbol.for('yaml.map'); + const PAIR = Symbol.for('yaml.pair'); + const SCALAR$1 = Symbol.for('yaml.scalar'); + const SEQ = Symbol.for('yaml.seq'); + const NODE_TYPE = Symbol.for('yaml.node.type'); + const isAlias = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === ALIAS; + const isDocument = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === DOC; + const isMap = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === MAP; + const isPair = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === PAIR; + const isScalar$1 = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === SCALAR$1; + const isSeq = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === SEQ; + function isCollection$1(node) { + if (node && typeof node === 'object') + switch (node[NODE_TYPE]) { + case MAP: + case SEQ: + return true; + } + return false; + } + function isNode(node) { + if (node && typeof node === 'object') + switch (node[NODE_TYPE]) { + case ALIAS: + case MAP: + case SCALAR$1: + case SEQ: + return true; + } + return false; + } + const hasAnchor = (node) => (isScalar$1(node) || isCollection$1(node)) && !!node.anchor; + + const BREAK$1 = Symbol('break visit'); + const SKIP$1 = Symbol('skip children'); + const REMOVE$1 = Symbol('remove node'); + /** + * Apply a visitor to an AST node or document. + * + * Walks through the tree (depth-first) starting from `node`, calling a + * `visitor` function with three arguments: + * - `key`: For sequence values and map `Pair`, the node's index in the + * collection. Within a `Pair`, `'key'` or `'value'`, correspondingly. + * `null` for the root node. + * - `node`: The current node. + * - `path`: The ancestry of the current node. + * + * The return value of the visitor may be used to control the traversal: + * - `undefined` (default): Do nothing and continue + * - `visit.SKIP`: Do not visit the children of this node, continue with next + * sibling + * - `visit.BREAK`: Terminate traversal completely + * - `visit.REMOVE`: Remove the current node, then continue with the next one + * - `Node`: Replace the current node, then continue by visiting it + * - `number`: While iterating the items of a sequence or map, set the index + * of the next step. This is useful especially if the index of the current + * node has changed. + * + * If `visitor` is a single function, it will be called with all values + * encountered in the tree, including e.g. `null` values. Alternatively, + * separate visitor functions may be defined for each `Map`, `Pair`, `Seq`, + * `Alias` and `Scalar` node. To define the same visitor function for more than + * one node type, use the `Collection` (map and seq), `Value` (map, seq & scalar) + * and `Node` (alias, map, seq & scalar) targets. Of all these, only the most + * specific defined one will be used for each node. + */ + function visit$1(node, visitor) { + const visitor_ = initVisitor(visitor); + if (isDocument(node)) { + const cd = visit_(null, node.contents, visitor_, Object.freeze([node])); + if (cd === REMOVE$1) + node.contents = null; + } + else + visit_(null, node, visitor_, Object.freeze([])); + } + // Without the `as symbol` casts, TS declares these in the `visit` + // namespace using `var`, but then complains about that because + // `unique symbol` must be `const`. + /** Terminate visit traversal completely */ + visit$1.BREAK = BREAK$1; + /** Do not visit the children of the current node */ + visit$1.SKIP = SKIP$1; + /** Remove the current node */ + visit$1.REMOVE = REMOVE$1; + function visit_(key, node, visitor, path) { + const ctrl = callVisitor(key, node, visitor, path); + if (isNode(ctrl) || isPair(ctrl)) { + replaceNode(key, path, ctrl); + return visit_(key, ctrl, visitor, path); + } + if (typeof ctrl !== 'symbol') { + if (isCollection$1(node)) { + path = Object.freeze(path.concat(node)); + for (let i = 0; i < node.items.length; ++i) { + const ci = visit_(i, node.items[i], visitor, path); + if (typeof ci === 'number') + i = ci - 1; + else if (ci === BREAK$1) + return BREAK$1; + else if (ci === REMOVE$1) { + node.items.splice(i, 1); + i -= 1; + } + } + } + else if (isPair(node)) { + path = Object.freeze(path.concat(node)); + const ck = visit_('key', node.key, visitor, path); + if (ck === BREAK$1) + return BREAK$1; + else if (ck === REMOVE$1) + node.key = null; + const cv = visit_('value', node.value, visitor, path); + if (cv === BREAK$1) + return BREAK$1; + else if (cv === REMOVE$1) + node.value = null; + } + } + return ctrl; + } + /** + * Apply an async visitor to an AST node or document. + * + * Walks through the tree (depth-first) starting from `node`, calling a + * `visitor` function with three arguments: + * - `key`: For sequence values and map `Pair`, the node's index in the + * collection. Within a `Pair`, `'key'` or `'value'`, correspondingly. + * `null` for the root node. + * - `node`: The current node. + * - `path`: The ancestry of the current node. + * + * The return value of the visitor may be used to control the traversal: + * - `Promise`: Must resolve to one of the following values + * - `undefined` (default): Do nothing and continue + * - `visit.SKIP`: Do not visit the children of this node, continue with next + * sibling + * - `visit.BREAK`: Terminate traversal completely + * - `visit.REMOVE`: Remove the current node, then continue with the next one + * - `Node`: Replace the current node, then continue by visiting it + * - `number`: While iterating the items of a sequence or map, set the index + * of the next step. This is useful especially if the index of the current + * node has changed. + * + * If `visitor` is a single function, it will be called with all values + * encountered in the tree, including e.g. `null` values. Alternatively, + * separate visitor functions may be defined for each `Map`, `Pair`, `Seq`, + * `Alias` and `Scalar` node. To define the same visitor function for more than + * one node type, use the `Collection` (map and seq), `Value` (map, seq & scalar) + * and `Node` (alias, map, seq & scalar) targets. Of all these, only the most + * specific defined one will be used for each node. + */ + async function visitAsync(node, visitor) { + const visitor_ = initVisitor(visitor); + if (isDocument(node)) { + const cd = await visitAsync_(null, node.contents, visitor_, Object.freeze([node])); + if (cd === REMOVE$1) + node.contents = null; + } + else + await visitAsync_(null, node, visitor_, Object.freeze([])); + } + // Without the `as symbol` casts, TS declares these in the `visit` + // namespace using `var`, but then complains about that because + // `unique symbol` must be `const`. + /** Terminate visit traversal completely */ + visitAsync.BREAK = BREAK$1; + /** Do not visit the children of the current node */ + visitAsync.SKIP = SKIP$1; + /** Remove the current node */ + visitAsync.REMOVE = REMOVE$1; + async function visitAsync_(key, node, visitor, path) { + const ctrl = await callVisitor(key, node, visitor, path); + if (isNode(ctrl) || isPair(ctrl)) { + replaceNode(key, path, ctrl); + return visitAsync_(key, ctrl, visitor, path); + } + if (typeof ctrl !== 'symbol') { + if (isCollection$1(node)) { + path = Object.freeze(path.concat(node)); + for (let i = 0; i < node.items.length; ++i) { + const ci = await visitAsync_(i, node.items[i], visitor, path); + if (typeof ci === 'number') + i = ci - 1; + else if (ci === BREAK$1) + return BREAK$1; + else if (ci === REMOVE$1) { + node.items.splice(i, 1); + i -= 1; + } + } + } + else if (isPair(node)) { + path = Object.freeze(path.concat(node)); + const ck = await visitAsync_('key', node.key, visitor, path); + if (ck === BREAK$1) + return BREAK$1; + else if (ck === REMOVE$1) + node.key = null; + const cv = await visitAsync_('value', node.value, visitor, path); + if (cv === BREAK$1) + return BREAK$1; + else if (cv === REMOVE$1) + node.value = null; + } + } + return ctrl; + } + function initVisitor(visitor) { + if (typeof visitor === 'object' && + (visitor.Collection || visitor.Node || visitor.Value)) { + return Object.assign({ + Alias: visitor.Node, + Map: visitor.Node, + Scalar: visitor.Node, + Seq: visitor.Node + }, visitor.Value && { + Map: visitor.Value, + Scalar: visitor.Value, + Seq: visitor.Value + }, visitor.Collection && { + Map: visitor.Collection, + Seq: visitor.Collection + }, visitor); + } + return visitor; + } + function callVisitor(key, node, visitor, path) { + if (typeof visitor === 'function') + return visitor(key, node, path); + if (isMap(node)) + return visitor.Map?.(key, node, path); + if (isSeq(node)) + return visitor.Seq?.(key, node, path); + if (isPair(node)) + return visitor.Pair?.(key, node, path); + if (isScalar$1(node)) + return visitor.Scalar?.(key, node, path); + if (isAlias(node)) + return visitor.Alias?.(key, node, path); + return undefined; + } + function replaceNode(key, path, node) { + const parent = path[path.length - 1]; + if (isCollection$1(parent)) { + parent.items[key] = node; + } + else if (isPair(parent)) { + if (key === 'key') + parent.key = node; + else + parent.value = node; + } + else if (isDocument(parent)) { + parent.contents = node; + } + else { + const pt = isAlias(parent) ? 'alias' : 'scalar'; + throw new Error(`Cannot replace node with ${pt} parent`); + } + } + + const escapeChars = { + '!': '%21', + ',': '%2C', + '[': '%5B', + ']': '%5D', + '{': '%7B', + '}': '%7D' + }; + const escapeTagName = (tn) => tn.replace(/[!,[\]{}]/g, ch => escapeChars[ch]); + class Directives { + constructor(yaml, tags) { + /** + * The directives-end/doc-start marker `---`. If `null`, a marker may still be + * included in the document's stringified representation. + */ + this.docStart = null; + /** The doc-end marker `...`. */ + this.docEnd = false; + this.yaml = Object.assign({}, Directives.defaultYaml, yaml); + this.tags = Object.assign({}, Directives.defaultTags, tags); + } + clone() { + const copy = new Directives(this.yaml, this.tags); + copy.docStart = this.docStart; + return copy; + } + /** + * During parsing, get a Directives instance for the current document and + * update the stream state according to the current version's spec. + */ + atDocument() { + const res = new Directives(this.yaml, this.tags); + switch (this.yaml.version) { + case '1.1': + this.atNextDocument = true; + break; + case '1.2': + this.atNextDocument = false; + this.yaml = { + explicit: Directives.defaultYaml.explicit, + version: '1.2' + }; + this.tags = Object.assign({}, Directives.defaultTags); + break; + } + return res; + } + /** + * @param onError - May be called even if the action was successful + * @returns `true` on success + */ + add(line, onError) { + if (this.atNextDocument) { + this.yaml = { explicit: Directives.defaultYaml.explicit, version: '1.1' }; + this.tags = Object.assign({}, Directives.defaultTags); + this.atNextDocument = false; + } + const parts = line.trim().split(/[ \t]+/); + const name = parts.shift(); + switch (name) { + case '%TAG': { + if (parts.length !== 2) { + onError(0, '%TAG directive should contain exactly two parts'); + if (parts.length < 2) + return false; + } + const [handle, prefix] = parts; + this.tags[handle] = prefix; + return true; + } + case '%YAML': { + this.yaml.explicit = true; + if (parts.length !== 1) { + onError(0, '%YAML directive should contain exactly one part'); + return false; + } + const [version] = parts; + if (version === '1.1' || version === '1.2') { + this.yaml.version = version; + return true; + } + else { + const isValid = /^\d+\.\d+$/.test(version); + onError(6, `Unsupported YAML version ${version}`, isValid); + return false; + } + } + default: + onError(0, `Unknown directive ${name}`, true); + return false; + } + } + /** + * Resolves a tag, matching handles to those defined in %TAG directives. + * + * @returns Resolved tag, which may also be the non-specific tag `'!'` or a + * `'!local'` tag, or `null` if unresolvable. + */ + tagName(source, onError) { + if (source === '!') + return '!'; // non-specific tag + if (source[0] !== '!') { + onError(`Not a valid tag: ${source}`); + return null; + } + if (source[1] === '<') { + const verbatim = source.slice(2, -1); + if (verbatim === '!' || verbatim === '!!') { + onError(`Verbatim tags aren't resolved, so ${source} is invalid.`); + return null; + } + if (source[source.length - 1] !== '>') + onError('Verbatim tags must end with a >'); + return verbatim; + } + const [, handle, suffix] = source.match(/^(.*!)([^!]*)$/s); + if (!suffix) + onError(`The ${source} tag has no suffix`); + const prefix = this.tags[handle]; + if (prefix) { + try { + return prefix + decodeURIComponent(suffix); + } + catch (error) { + onError(String(error)); + return null; + } + } + if (handle === '!') + return source; // local tag + onError(`Could not resolve tag: ${source}`); + return null; + } + /** + * Given a fully resolved tag, returns its printable string form, + * taking into account current tag prefixes and defaults. + */ + tagString(tag) { + for (const [handle, prefix] of Object.entries(this.tags)) { + if (tag.startsWith(prefix)) + return handle + escapeTagName(tag.substring(prefix.length)); + } + return tag[0] === '!' ? tag : `!<${tag}>`; + } + toString(doc) { + const lines = this.yaml.explicit + ? [`%YAML ${this.yaml.version || '1.2'}`] + : []; + const tagEntries = Object.entries(this.tags); + let tagNames; + if (doc && tagEntries.length > 0 && isNode(doc.contents)) { + const tags = {}; + visit$1(doc.contents, (_key, node) => { + if (isNode(node) && node.tag) + tags[node.tag] = true; + }); + tagNames = Object.keys(tags); + } + else + tagNames = []; + for (const [handle, prefix] of tagEntries) { + if (handle === '!!' && prefix === 'tag:yaml.org,2002:') + continue; + if (!doc || tagNames.some(tn => tn.startsWith(prefix))) + lines.push(`%TAG ${handle} ${prefix}`); + } + return lines.join('\n'); + } + } + Directives.defaultYaml = { explicit: false, version: '1.2' }; + Directives.defaultTags = { '!!': 'tag:yaml.org,2002:' }; + + /** + * Verify that the input string is a valid anchor. + * + * Will throw on errors. + */ + function anchorIsValid(anchor) { + if (/[\x00-\x19\s,[\]{}]/.test(anchor)) { + const sa = JSON.stringify(anchor); + const msg = `Anchor must not contain whitespace or control characters: ${sa}`; + throw new Error(msg); + } + return true; + } + function anchorNames(root) { + const anchors = new Set(); + visit$1(root, { + Value(_key, node) { + if (node.anchor) + anchors.add(node.anchor); + } + }); + return anchors; + } + /** Find a new anchor name with the given `prefix` and a one-indexed suffix. */ + function findNewAnchor(prefix, exclude) { + for (let i = 1; true; ++i) { + const name = `${prefix}${i}`; + if (!exclude.has(name)) + return name; + } + } + function createNodeAnchors(doc, prefix) { + const aliasObjects = []; + const sourceObjects = new Map(); + let prevAnchors = null; + return { + onAnchor: (source) => { + aliasObjects.push(source); + if (!prevAnchors) + prevAnchors = anchorNames(doc); + const anchor = findNewAnchor(prefix, prevAnchors); + prevAnchors.add(anchor); + return anchor; + }, + /** + * With circular references, the source node is only resolved after all + * of its child nodes are. This is why anchors are set only after all of + * the nodes have been created. + */ + setAnchors: () => { + for (const source of aliasObjects) { + const ref = sourceObjects.get(source); + if (typeof ref === 'object' && + ref.anchor && + (isScalar$1(ref.node) || isCollection$1(ref.node))) { + ref.node.anchor = ref.anchor; + } + else { + const error = new Error('Failed to resolve repeated object (this should not happen)'); + error.source = source; + throw error; + } + } + }, + sourceObjects + }; + } + + /** + * Applies the JSON.parse reviver algorithm as defined in the ECMA-262 spec, + * in section 24.5.1.1 "Runtime Semantics: InternalizeJSONProperty" of the + * 2021 edition: https://tc39.es/ecma262/#sec-json.parse + * + * Includes extensions for handling Map and Set objects. + */ + function applyReviver(reviver, obj, key, val) { + if (val && typeof val === 'object') { + if (Array.isArray(val)) { + for (let i = 0, len = val.length; i < len; ++i) { + const v0 = val[i]; + const v1 = applyReviver(reviver, val, String(i), v0); + // eslint-disable-next-line @typescript-eslint/no-array-delete + if (v1 === undefined) + delete val[i]; + else if (v1 !== v0) + val[i] = v1; + } + } + else if (val instanceof Map) { + for (const k of Array.from(val.keys())) { + const v0 = val.get(k); + const v1 = applyReviver(reviver, val, k, v0); + if (v1 === undefined) + val.delete(k); + else if (v1 !== v0) + val.set(k, v1); + } + } + else if (val instanceof Set) { + for (const v0 of Array.from(val)) { + const v1 = applyReviver(reviver, val, v0, v0); + if (v1 === undefined) + val.delete(v0); + else if (v1 !== v0) { + val.delete(v0); + val.add(v1); + } + } + } + else { + for (const [k, v0] of Object.entries(val)) { + const v1 = applyReviver(reviver, val, k, v0); + if (v1 === undefined) + delete val[k]; + else if (v1 !== v0) + val[k] = v1; + } + } + } + return reviver.call(obj, key, val); + } + + /** + * Recursively convert any node or its contents to native JavaScript + * + * @param value - The input value + * @param arg - If `value` defines a `toJSON()` method, use this + * as its first argument + * @param ctx - Conversion context, originally set in Document#toJS(). If + * `{ keep: true }` is not set, output should be suitable for JSON + * stringification. + */ + function toJS(value, arg, ctx) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + if (Array.isArray(value)) + return value.map((v, i) => toJS(v, String(i), ctx)); + if (value && typeof value.toJSON === 'function') { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + if (!ctx || !hasAnchor(value)) + return value.toJSON(arg, ctx); + const data = { aliasCount: 0, count: 1, res: undefined }; + ctx.anchors.set(value, data); + ctx.onCreate = res => { + data.res = res; + delete ctx.onCreate; + }; + const res = value.toJSON(arg, ctx); + if (ctx.onCreate) + ctx.onCreate(res); + return res; + } + if (typeof value === 'bigint' && !ctx?.keep) + return Number(value); + return value; + } + + class NodeBase { + constructor(type) { + Object.defineProperty(this, NODE_TYPE, { value: type }); + } + /** Create a copy of this node. */ + clone() { + const copy = Object.create(Object.getPrototypeOf(this), Object.getOwnPropertyDescriptors(this)); + if (this.range) + copy.range = this.range.slice(); + return copy; + } + /** A plain JavaScript representation of this node. */ + toJS(doc, { mapAsMap, maxAliasCount, onAnchor, reviver } = {}) { + if (!isDocument(doc)) + throw new TypeError('A document argument is required'); + const ctx = { + anchors: new Map(), + doc, + keep: true, + mapAsMap: mapAsMap === true, + mapKeyWarned: false, + maxAliasCount: typeof maxAliasCount === 'number' ? maxAliasCount : 100 + }; + const res = toJS(this, '', ctx); + if (typeof onAnchor === 'function') + for (const { count, res } of ctx.anchors.values()) + onAnchor(res, count); + return typeof reviver === 'function' + ? applyReviver(reviver, { '': res }, '', res) + : res; + } + } + + class Alias extends NodeBase { + constructor(source) { + super(ALIAS); + this.source = source; + Object.defineProperty(this, 'tag', { + set() { + throw new Error('Alias nodes cannot have tags'); + } + }); + } + /** + * Resolve the value of this alias within `doc`, finding the last + * instance of the `source` anchor before this node. + */ + resolve(doc) { + let found = undefined; + visit$1(doc, { + Node: (_key, node) => { + if (node === this) + return visit$1.BREAK; + if (node.anchor === this.source) + found = node; + } + }); + return found; + } + toJSON(_arg, ctx) { + if (!ctx) + return { source: this.source }; + const { anchors, doc, maxAliasCount } = ctx; + const source = this.resolve(doc); + if (!source) { + const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`; + throw new ReferenceError(msg); + } + let data = anchors.get(source); + if (!data) { + // Resolve anchors for Node.prototype.toJS() + toJS(source, null, ctx); + data = anchors.get(source); + } + /* istanbul ignore if */ + if (!data || data.res === undefined) { + const msg = 'This should not happen: Alias anchor was not resolved?'; + throw new ReferenceError(msg); + } + if (maxAliasCount >= 0) { + data.count += 1; + if (data.aliasCount === 0) + data.aliasCount = getAliasCount(doc, source, anchors); + if (data.count * data.aliasCount > maxAliasCount) { + const msg = 'Excessive alias count indicates a resource exhaustion attack'; + throw new ReferenceError(msg); + } + } + return data.res; + } + toString(ctx, _onComment, _onChompKeep) { + const src = `*${this.source}`; + if (ctx) { + anchorIsValid(this.source); + if (ctx.options.verifyAliasOrder && !ctx.anchors.has(this.source)) { + const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`; + throw new Error(msg); + } + if (ctx.implicitKey) + return `${src} `; + } + return src; + } + } + function getAliasCount(doc, node, anchors) { + if (isAlias(node)) { + const source = node.resolve(doc); + const anchor = anchors && source && anchors.get(source); + return anchor ? anchor.count * anchor.aliasCount : 0; + } + else if (isCollection$1(node)) { + let count = 0; + for (const item of node.items) { + const c = getAliasCount(doc, item, anchors); + if (c > count) + count = c; + } + return count; + } + else if (isPair(node)) { + const kc = getAliasCount(doc, node.key, anchors); + const vc = getAliasCount(doc, node.value, anchors); + return Math.max(kc, vc); + } + return 1; + } + + const isScalarValue = (value) => !value || (typeof value !== 'function' && typeof value !== 'object'); + class Scalar extends NodeBase { + constructor(value) { + super(SCALAR$1); + this.value = value; + } + toJSON(arg, ctx) { + return ctx?.keep ? this.value : toJS(this.value, arg, ctx); + } + toString() { + return String(this.value); + } + } + Scalar.BLOCK_FOLDED = 'BLOCK_FOLDED'; + Scalar.BLOCK_LITERAL = 'BLOCK_LITERAL'; + Scalar.PLAIN = 'PLAIN'; + Scalar.QUOTE_DOUBLE = 'QUOTE_DOUBLE'; + Scalar.QUOTE_SINGLE = 'QUOTE_SINGLE'; + + const defaultTagPrefix = 'tag:yaml.org,2002:'; + function findTagObject(value, tagName, tags) { + if (tagName) { + const match = tags.filter(t => t.tag === tagName); + const tagObj = match.find(t => !t.format) ?? match[0]; + if (!tagObj) + throw new Error(`Tag ${tagName} not found`); + return tagObj; + } + return tags.find(t => t.identify?.(value) && !t.format); + } + function createNode(value, tagName, ctx) { + if (isDocument(value)) + value = value.contents; + if (isNode(value)) + return value; + if (isPair(value)) { + const map = ctx.schema[MAP].createNode?.(ctx.schema, null, ctx); + map.items.push(value); + return map; + } + if (value instanceof String || + value instanceof Number || + value instanceof Boolean || + (typeof BigInt !== 'undefined' && value instanceof BigInt) // not supported everywhere + ) { + // https://tc39.es/ecma262/#sec-serializejsonproperty + value = value.valueOf(); + } + const { aliasDuplicateObjects, onAnchor, onTagObj, schema, sourceObjects } = ctx; + // Detect duplicate references to the same object & use Alias nodes for all + // after first. The `ref` wrapper allows for circular references to resolve. + let ref = undefined; + if (aliasDuplicateObjects && value && typeof value === 'object') { + ref = sourceObjects.get(value); + if (ref) { + if (!ref.anchor) + ref.anchor = onAnchor(value); + return new Alias(ref.anchor); + } + else { + ref = { anchor: null, node: null }; + sourceObjects.set(value, ref); + } + } + if (tagName?.startsWith('!!')) + tagName = defaultTagPrefix + tagName.slice(2); + let tagObj = findTagObject(value, tagName, schema.tags); + if (!tagObj) { + if (value && typeof value.toJSON === 'function') { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + value = value.toJSON(); + } + if (!value || typeof value !== 'object') { + const node = new Scalar(value); + if (ref) + ref.node = node; + return node; + } + tagObj = + value instanceof Map + ? schema[MAP] + : Symbol.iterator in Object(value) + ? schema[SEQ] + : schema[MAP]; + } + if (onTagObj) { + onTagObj(tagObj); + delete ctx.onTagObj; + } + const node = tagObj?.createNode + ? tagObj.createNode(ctx.schema, value, ctx) + : typeof tagObj?.nodeClass?.from === 'function' + ? tagObj.nodeClass.from(ctx.schema, value, ctx) + : new Scalar(value); + if (tagName) + node.tag = tagName; + else if (!tagObj.default) + node.tag = tagObj.tag; + if (ref) + ref.node = node; + return node; + } + + function collectionFromPath(schema, path, value) { + let v = value; + for (let i = path.length - 1; i >= 0; --i) { + const k = path[i]; + if (typeof k === 'number' && Number.isInteger(k) && k >= 0) { + const a = []; + a[k] = v; + v = a; + } + else { + v = new Map([[k, v]]); + } + } + return createNode(v, undefined, { + aliasDuplicateObjects: false, + keepUndefined: false, + onAnchor: () => { + throw new Error('This should not happen, please report a bug.'); + }, + schema, + sourceObjects: new Map() + }); + } + // Type guard is intentionally a little wrong so as to be more useful, + // as it does not cover untypable empty non-string iterables (e.g. []). + const isEmptyPath = (path) => path == null || + (typeof path === 'object' && !!path[Symbol.iterator]().next().done); + class Collection extends NodeBase { + constructor(type, schema) { + super(type); + Object.defineProperty(this, 'schema', { + value: schema, + configurable: true, + enumerable: false, + writable: true + }); + } + /** + * Create a copy of this collection. + * + * @param schema - If defined, overwrites the original's schema + */ + clone(schema) { + const copy = Object.create(Object.getPrototypeOf(this), Object.getOwnPropertyDescriptors(this)); + if (schema) + copy.schema = schema; + copy.items = copy.items.map(it => isNode(it) || isPair(it) ? it.clone(schema) : it); + if (this.range) + copy.range = this.range.slice(); + return copy; + } + /** + * Adds a value to the collection. For `!!map` and `!!omap` the value must + * be a Pair instance or a `{ key, value }` object, which may not have a key + * that already exists in the map. + */ + addIn(path, value) { + if (isEmptyPath(path)) + this.add(value); + else { + const [key, ...rest] = path; + const node = this.get(key, true); + if (isCollection$1(node)) + node.addIn(rest, value); + else if (node === undefined && this.schema) + this.set(key, collectionFromPath(this.schema, rest, value)); + else + throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`); + } + } + /** + * Removes a value from the collection. + * @returns `true` if the item was found and removed. + */ + deleteIn(path) { + const [key, ...rest] = path; + if (rest.length === 0) + return this.delete(key); + const node = this.get(key, true); + if (isCollection$1(node)) + return node.deleteIn(rest); + else + throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`); + } + /** + * Returns item at `key`, or `undefined` if not found. By default unwraps + * scalar values from their surrounding node; to disable set `keepScalar` to + * `true` (collections are always returned intact). + */ + getIn(path, keepScalar) { + const [key, ...rest] = path; + const node = this.get(key, true); + if (rest.length === 0) + return !keepScalar && isScalar$1(node) ? node.value : node; + else + return isCollection$1(node) ? node.getIn(rest, keepScalar) : undefined; + } + hasAllNullValues(allowScalar) { + return this.items.every(node => { + if (!isPair(node)) + return false; + const n = node.value; + return (n == null || + (allowScalar && + isScalar$1(n) && + n.value == null && + !n.commentBefore && + !n.comment && + !n.tag)); + }); + } + /** + * Checks if the collection includes a value with the key `key`. + */ + hasIn(path) { + const [key, ...rest] = path; + if (rest.length === 0) + return this.has(key); + const node = this.get(key, true); + return isCollection$1(node) ? node.hasIn(rest) : false; + } + /** + * Sets a value in this collection. For `!!set`, `value` needs to be a + * boolean to add/remove the item from the set. + */ + setIn(path, value) { + const [key, ...rest] = path; + if (rest.length === 0) { + this.set(key, value); + } + else { + const node = this.get(key, true); + if (isCollection$1(node)) + node.setIn(rest, value); + else if (node === undefined && this.schema) + this.set(key, collectionFromPath(this.schema, rest, value)); + else + throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`); + } + } + } + + /** + * Stringifies a comment. + * + * Empty comment lines are left empty, + * lines consisting of a single space are replaced by `#`, + * and all other lines are prefixed with a `#`. + */ + const stringifyComment = (str) => str.replace(/^(?!$)(?: $)?/gm, '#'); + function indentComment(comment, indent) { + if (/^\n+$/.test(comment)) + return comment.substring(1); + return indent ? comment.replace(/^(?! *$)/gm, indent) : comment; + } + const lineComment = (str, indent, comment) => str.endsWith('\n') + ? indentComment(comment, indent) + : comment.includes('\n') + ? '\n' + indentComment(comment, indent) + : (str.endsWith(' ') ? '' : ' ') + comment; + + const FOLD_FLOW = 'flow'; + const FOLD_BLOCK = 'block'; + const FOLD_QUOTED = 'quoted'; + /** + * Tries to keep input at up to `lineWidth` characters, splitting only on spaces + * not followed by newlines or spaces unless `mode` is `'quoted'`. Lines are + * terminated with `\n` and started with `indent`. + */ + function foldFlowLines(text, indent, mode = 'flow', { indentAtStart, lineWidth = 80, minContentWidth = 20, onFold, onOverflow } = {}) { + if (!lineWidth || lineWidth < 0) + return text; + if (lineWidth < minContentWidth) + minContentWidth = 0; + const endStep = Math.max(1 + minContentWidth, 1 + lineWidth - indent.length); + if (text.length <= endStep) + return text; + const folds = []; + const escapedFolds = {}; + let end = lineWidth - indent.length; + if (typeof indentAtStart === 'number') { + if (indentAtStart > lineWidth - Math.max(2, minContentWidth)) + folds.push(0); + else + end = lineWidth - indentAtStart; + } + let split = undefined; + let prev = undefined; + let overflow = false; + let i = -1; + let escStart = -1; + let escEnd = -1; + if (mode === FOLD_BLOCK) { + i = consumeMoreIndentedLines(text, i, indent.length); + if (i !== -1) + end = i + endStep; + } + for (let ch; (ch = text[(i += 1)]);) { + if (mode === FOLD_QUOTED && ch === '\\') { + escStart = i; + switch (text[i + 1]) { + case 'x': + i += 3; + break; + case 'u': + i += 5; + break; + case 'U': + i += 9; + break; + default: + i += 1; + } + escEnd = i; + } + if (ch === '\n') { + if (mode === FOLD_BLOCK) + i = consumeMoreIndentedLines(text, i, indent.length); + end = i + indent.length + endStep; + split = undefined; + } + else { + if (ch === ' ' && + prev && + prev !== ' ' && + prev !== '\n' && + prev !== '\t') { + // space surrounded by non-space can be replaced with newline + indent + const next = text[i + 1]; + if (next && next !== ' ' && next !== '\n' && next !== '\t') + split = i; + } + if (i >= end) { + if (split) { + folds.push(split); + end = split + endStep; + split = undefined; + } + else if (mode === FOLD_QUOTED) { + // white-space collected at end may stretch past lineWidth + while (prev === ' ' || prev === '\t') { + prev = ch; + ch = text[(i += 1)]; + overflow = true; + } + // Account for newline escape, but don't break preceding escape + const j = i > escEnd + 1 ? i - 2 : escStart - 1; + // Bail out if lineWidth & minContentWidth are shorter than an escape string + if (escapedFolds[j]) + return text; + folds.push(j); + escapedFolds[j] = true; + end = j + endStep; + split = undefined; + } + else { + overflow = true; + } + } + } + prev = ch; + } + if (overflow && onOverflow) + onOverflow(); + if (folds.length === 0) + return text; + if (onFold) + onFold(); + let res = text.slice(0, folds[0]); + for (let i = 0; i < folds.length; ++i) { + const fold = folds[i]; + const end = folds[i + 1] || text.length; + if (fold === 0) + res = `\n${indent}${text.slice(0, end)}`; + else { + if (mode === FOLD_QUOTED && escapedFolds[fold]) + res += `${text[fold]}\\`; + res += `\n${indent}${text.slice(fold + 1, end)}`; + } + } + return res; + } + /** + * Presumes `i + 1` is at the start of a line + * @returns index of last newline in more-indented block + */ + function consumeMoreIndentedLines(text, i, indent) { + let end = i; + let start = i + 1; + let ch = text[start]; + while (ch === ' ' || ch === '\t') { + if (i < start + indent) { + ch = text[++i]; + } + else { + do { + ch = text[++i]; + } while (ch && ch !== '\n'); + end = i; + start = i + 1; + ch = text[start]; + } + } + return end; + } + + const getFoldOptions = (ctx, isBlock) => ({ + indentAtStart: isBlock ? ctx.indent.length : ctx.indentAtStart, + lineWidth: ctx.options.lineWidth, + minContentWidth: ctx.options.minContentWidth + }); + // Also checks for lines starting with %, as parsing the output as YAML 1.1 will + // presume that's starting a new document. + const containsDocumentMarker = (str) => /^(%|---|\.\.\.)/m.test(str); + function lineLengthOverLimit(str, lineWidth, indentLength) { + if (!lineWidth || lineWidth < 0) + return false; + const limit = lineWidth - indentLength; + const strLen = str.length; + if (strLen <= limit) + return false; + for (let i = 0, start = 0; i < strLen; ++i) { + if (str[i] === '\n') { + if (i - start > limit) + return true; + start = i + 1; + if (strLen - start <= limit) + return false; + } + } + return true; + } + function doubleQuotedString(value, ctx) { + const json = JSON.stringify(value); + if (ctx.options.doubleQuotedAsJSON) + return json; + const { implicitKey } = ctx; + const minMultiLineLength = ctx.options.doubleQuotedMinMultiLineLength; + const indent = ctx.indent || (containsDocumentMarker(value) ? ' ' : ''); + let str = ''; + let start = 0; + for (let i = 0, ch = json[i]; ch; ch = json[++i]) { + if (ch === ' ' && json[i + 1] === '\\' && json[i + 2] === 'n') { + // space before newline needs to be escaped to not be folded + str += json.slice(start, i) + '\\ '; + i += 1; + start = i; + ch = '\\'; + } + if (ch === '\\') + switch (json[i + 1]) { + case 'u': + { + str += json.slice(start, i); + const code = json.substr(i + 2, 4); + switch (code) { + case '0000': + str += '\\0'; + break; + case '0007': + str += '\\a'; + break; + case '000b': + str += '\\v'; + break; + case '001b': + str += '\\e'; + break; + case '0085': + str += '\\N'; + break; + case '00a0': + str += '\\_'; + break; + case '2028': + str += '\\L'; + break; + case '2029': + str += '\\P'; + break; + default: + if (code.substr(0, 2) === '00') + str += '\\x' + code.substr(2); + else + str += json.substr(i, 6); + } + i += 5; + start = i + 1; + } + break; + case 'n': + if (implicitKey || + json[i + 2] === '"' || + json.length < minMultiLineLength) { + i += 1; + } + else { + // folding will eat first newline + str += json.slice(start, i) + '\n\n'; + while (json[i + 2] === '\\' && + json[i + 3] === 'n' && + json[i + 4] !== '"') { + str += '\n'; + i += 2; + } + str += indent; + // space after newline needs to be escaped to not be folded + if (json[i + 2] === ' ') + str += '\\'; + i += 1; + start = i + 1; + } + break; + default: + i += 1; + } + } + str = start ? str + json.slice(start) : json; + return implicitKey + ? str + : foldFlowLines(str, indent, FOLD_QUOTED, getFoldOptions(ctx, false)); + } + function singleQuotedString(value, ctx) { + if (ctx.options.singleQuote === false || + (ctx.implicitKey && value.includes('\n')) || + /[ \t]\n|\n[ \t]/.test(value) // single quoted string can't have leading or trailing whitespace around newline + ) + return doubleQuotedString(value, ctx); + const indent = ctx.indent || (containsDocumentMarker(value) ? ' ' : ''); + const res = "'" + value.replace(/'/g, "''").replace(/\n+/g, `$&\n${indent}`) + "'"; + return ctx.implicitKey + ? res + : foldFlowLines(res, indent, FOLD_FLOW, getFoldOptions(ctx, false)); + } + function quotedString(value, ctx) { + const { singleQuote } = ctx.options; + let qs; + if (singleQuote === false) + qs = doubleQuotedString; + else { + const hasDouble = value.includes('"'); + const hasSingle = value.includes("'"); + if (hasDouble && !hasSingle) + qs = singleQuotedString; + else if (hasSingle && !hasDouble) + qs = doubleQuotedString; + else + qs = singleQuote ? singleQuotedString : doubleQuotedString; + } + return qs(value, ctx); + } + // The negative lookbehind avoids a polynomial search, + // but isn't supported yet on Safari: https://caniuse.com/js-regexp-lookbehind + let blockEndNewlines; + try { + blockEndNewlines = new RegExp('(^|(?\n'; + // determine chomping from whitespace at value end + let chomp; + let endStart; + for (endStart = value.length; endStart > 0; --endStart) { + const ch = value[endStart - 1]; + if (ch !== '\n' && ch !== '\t' && ch !== ' ') + break; + } + let end = value.substring(endStart); + const endNlPos = end.indexOf('\n'); + if (endNlPos === -1) { + chomp = '-'; // strip + } + else if (value === end || endNlPos !== end.length - 1) { + chomp = '+'; // keep + if (onChompKeep) + onChompKeep(); + } + else { + chomp = ''; // clip + } + if (end) { + value = value.slice(0, -end.length); + if (end[end.length - 1] === '\n') + end = end.slice(0, -1); + end = end.replace(blockEndNewlines, `$&${indent}`); + } + // determine indent indicator from whitespace at value start + let startWithSpace = false; + let startEnd; + let startNlPos = -1; + for (startEnd = 0; startEnd < value.length; ++startEnd) { + const ch = value[startEnd]; + if (ch === ' ') + startWithSpace = true; + else if (ch === '\n') + startNlPos = startEnd; + else + break; + } + let start = value.substring(0, startNlPos < startEnd ? startNlPos + 1 : startEnd); + if (start) { + value = value.substring(start.length); + start = start.replace(/\n+/g, `$&${indent}`); + } + const indentSize = indent ? '2' : '1'; // root is at -1 + let header = (literal ? '|' : '>') + (startWithSpace ? indentSize : '') + chomp; + if (comment) { + header += ' ' + commentString(comment.replace(/ ?[\r\n]+/g, ' ')); + if (onComment) + onComment(); + } + if (literal) { + value = value.replace(/\n+/g, `$&${indent}`); + return `${header}\n${indent}${start}${value}${end}`; + } + value = value + .replace(/\n+/g, '\n$&') + .replace(/(?:^|\n)([\t ].*)(?:([\n\t ]*)\n(?![\n\t ]))?/g, '$1$2') // more-indented lines aren't folded + // ^ more-ind. ^ empty ^ capture next empty lines only at end of indent + .replace(/\n+/g, `$&${indent}`); + const body = foldFlowLines(`${start}${value}${end}`, indent, FOLD_BLOCK, getFoldOptions(ctx, true)); + return `${header}\n${indent}${body}`; + } + function plainString(item, ctx, onComment, onChompKeep) { + const { type, value } = item; + const { actualString, implicitKey, indent, indentStep, inFlow } = ctx; + if ((implicitKey && value.includes('\n')) || + (inFlow && /[[\]{},]/.test(value))) { + return quotedString(value, ctx); + } + if (!value || + /^[\n\t ,[\]{}#&*!|>'"%@`]|^[?-]$|^[?-][ \t]|[\n:][ \t]|[ \t]\n|[\n\t ]#|[\n\t :]$/.test(value)) { + // not allowed: + // - empty string, '-' or '?' + // - start with an indicator character (except [?:-]) or /[?-] / + // - '\n ', ': ' or ' \n' anywhere + // - '#' not preceded by a non-space char + // - end with ' ' or ':' + return implicitKey || inFlow || !value.includes('\n') + ? quotedString(value, ctx) + : blockString(item, ctx, onComment, onChompKeep); + } + if (!implicitKey && + !inFlow && + type !== Scalar.PLAIN && + value.includes('\n')) { + // Where allowed & type not set explicitly, prefer block style for multiline strings + return blockString(item, ctx, onComment, onChompKeep); + } + if (containsDocumentMarker(value)) { + if (indent === '') { + ctx.forceBlockIndent = true; + return blockString(item, ctx, onComment, onChompKeep); + } + else if (implicitKey && indent === indentStep) { + return quotedString(value, ctx); + } + } + const str = value.replace(/\n+/g, `$&\n${indent}`); + // Verify that output will be parsed as a string, as e.g. plain numbers and + // booleans get parsed with those types in v1.2 (e.g. '42', 'true' & '0.9e-3'), + // and others in v1.1. + if (actualString) { + const test = (tag) => tag.default && tag.tag !== 'tag:yaml.org,2002:str' && tag.test?.test(str); + const { compat, tags } = ctx.doc.schema; + if (tags.some(test) || compat?.some(test)) + return quotedString(value, ctx); + } + return implicitKey + ? str + : foldFlowLines(str, indent, FOLD_FLOW, getFoldOptions(ctx, false)); + } + function stringifyString(item, ctx, onComment, onChompKeep) { + const { implicitKey, inFlow } = ctx; + const ss = typeof item.value === 'string' + ? item + : Object.assign({}, item, { value: String(item.value) }); + let { type } = item; + if (type !== Scalar.QUOTE_DOUBLE) { + // force double quotes on control characters & unpaired surrogates + if (/[\x00-\x08\x0b-\x1f\x7f-\x9f\u{D800}-\u{DFFF}]/u.test(ss.value)) + type = Scalar.QUOTE_DOUBLE; + } + const _stringify = (_type) => { + switch (_type) { + case Scalar.BLOCK_FOLDED: + case Scalar.BLOCK_LITERAL: + return implicitKey || inFlow + ? quotedString(ss.value, ctx) // blocks are not valid inside flow containers + : blockString(ss, ctx, onComment, onChompKeep); + case Scalar.QUOTE_DOUBLE: + return doubleQuotedString(ss.value, ctx); + case Scalar.QUOTE_SINGLE: + return singleQuotedString(ss.value, ctx); + case Scalar.PLAIN: + return plainString(ss, ctx, onComment, onChompKeep); + default: + return null; + } + }; + let res = _stringify(type); + if (res === null) { + const { defaultKeyType, defaultStringType } = ctx.options; + const t = (implicitKey && defaultKeyType) || defaultStringType; + res = _stringify(t); + if (res === null) + throw new Error(`Unsupported default string type ${t}`); + } + return res; + } + + function createStringifyContext(doc, options) { + const opt = Object.assign({ + blockQuote: true, + commentString: stringifyComment, + defaultKeyType: null, + defaultStringType: 'PLAIN', + directives: null, + doubleQuotedAsJSON: false, + doubleQuotedMinMultiLineLength: 40, + falseStr: 'false', + flowCollectionPadding: true, + indentSeq: true, + lineWidth: 80, + minContentWidth: 20, + nullStr: 'null', + simpleKeys: false, + singleQuote: null, + trueStr: 'true', + verifyAliasOrder: true + }, doc.schema.toStringOptions, options); + let inFlow; + switch (opt.collectionStyle) { + case 'block': + inFlow = false; + break; + case 'flow': + inFlow = true; + break; + default: + inFlow = null; + } + return { + anchors: new Set(), + doc, + flowCollectionPadding: opt.flowCollectionPadding ? ' ' : '', + indent: '', + indentStep: typeof opt.indent === 'number' ? ' '.repeat(opt.indent) : ' ', + inFlow, + options: opt + }; + } + function getTagObject(tags, item) { + if (item.tag) { + const match = tags.filter(t => t.tag === item.tag); + if (match.length > 0) + return match.find(t => t.format === item.format) ?? match[0]; + } + let tagObj = undefined; + let obj; + if (isScalar$1(item)) { + obj = item.value; + const match = tags.filter(t => t.identify?.(obj)); + tagObj = + match.find(t => t.format === item.format) ?? match.find(t => !t.format); + } + else { + obj = item; + tagObj = tags.find(t => t.nodeClass && obj instanceof t.nodeClass); + } + if (!tagObj) { + const name = obj?.constructor?.name ?? typeof obj; + throw new Error(`Tag not resolved for ${name} value`); + } + return tagObj; + } + // needs to be called before value stringifier to allow for circular anchor refs + function stringifyProps(node, tagObj, { anchors, doc }) { + if (!doc.directives) + return ''; + const props = []; + const anchor = (isScalar$1(node) || isCollection$1(node)) && node.anchor; + if (anchor && anchorIsValid(anchor)) { + anchors.add(anchor); + props.push(`&${anchor}`); + } + const tag = node.tag ? node.tag : tagObj.default ? null : tagObj.tag; + if (tag) + props.push(doc.directives.tagString(tag)); + return props.join(' '); + } + function stringify$2(item, ctx, onComment, onChompKeep) { + if (isPair(item)) + return item.toString(ctx, onComment, onChompKeep); + if (isAlias(item)) { + if (ctx.doc.directives) + return item.toString(ctx); + if (ctx.resolvedAliases?.has(item)) { + throw new TypeError(`Cannot stringify circular structure without alias nodes`); + } + else { + if (ctx.resolvedAliases) + ctx.resolvedAliases.add(item); + else + ctx.resolvedAliases = new Set([item]); + item = item.resolve(ctx.doc); + } + } + let tagObj = undefined; + const node = isNode(item) + ? item + : ctx.doc.createNode(item, { onTagObj: o => (tagObj = o) }); + if (!tagObj) + tagObj = getTagObject(ctx.doc.schema.tags, node); + const props = stringifyProps(node, tagObj, ctx); + if (props.length > 0) + ctx.indentAtStart = (ctx.indentAtStart ?? 0) + props.length + 1; + const str = typeof tagObj.stringify === 'function' + ? tagObj.stringify(node, ctx, onComment, onChompKeep) + : isScalar$1(node) + ? stringifyString(node, ctx, onComment, onChompKeep) + : node.toString(ctx, onComment, onChompKeep); + if (!props) + return str; + return isScalar$1(node) || str[0] === '{' || str[0] === '[' + ? `${props} ${str}` + : `${props}\n${ctx.indent}${str}`; + } + + function stringifyPair({ key, value }, ctx, onComment, onChompKeep) { + const { allNullValues, doc, indent, indentStep, options: { commentString, indentSeq, simpleKeys } } = ctx; + let keyComment = (isNode(key) && key.comment) || null; + if (simpleKeys) { + if (keyComment) { + throw new Error('With simple keys, key nodes cannot have comments'); + } + if (isCollection$1(key) || (!isNode(key) && typeof key === 'object')) { + const msg = 'With simple keys, collection cannot be used as a key value'; + throw new Error(msg); + } + } + let explicitKey = !simpleKeys && + (!key || + (keyComment && value == null && !ctx.inFlow) || + isCollection$1(key) || + (isScalar$1(key) + ? key.type === Scalar.BLOCK_FOLDED || key.type === Scalar.BLOCK_LITERAL + : typeof key === 'object')); + ctx = Object.assign({}, ctx, { + allNullValues: false, + implicitKey: !explicitKey && (simpleKeys || !allNullValues), + indent: indent + indentStep + }); + let keyCommentDone = false; + let chompKeep = false; + let str = stringify$2(key, ctx, () => (keyCommentDone = true), () => (chompKeep = true)); + if (!explicitKey && !ctx.inFlow && str.length > 1024) { + if (simpleKeys) + throw new Error('With simple keys, single line scalar must not span more than 1024 characters'); + explicitKey = true; + } + if (ctx.inFlow) { + if (allNullValues || value == null) { + if (keyCommentDone && onComment) + onComment(); + return str === '' ? '?' : explicitKey ? `? ${str}` : str; + } + } + else if ((allNullValues && !simpleKeys) || (value == null && explicitKey)) { + str = `? ${str}`; + if (keyComment && !keyCommentDone) { + str += lineComment(str, ctx.indent, commentString(keyComment)); + } + else if (chompKeep && onChompKeep) + onChompKeep(); + return str; + } + if (keyCommentDone) + keyComment = null; + if (explicitKey) { + if (keyComment) + str += lineComment(str, ctx.indent, commentString(keyComment)); + str = `? ${str}\n${indent}:`; + } + else { + str = `${str}:`; + if (keyComment) + str += lineComment(str, ctx.indent, commentString(keyComment)); + } + let vsb, vcb, valueComment; + if (isNode(value)) { + vsb = !!value.spaceBefore; + vcb = value.commentBefore; + valueComment = value.comment; + } + else { + vsb = false; + vcb = null; + valueComment = null; + if (value && typeof value === 'object') + value = doc.createNode(value); + } + ctx.implicitKey = false; + if (!explicitKey && !keyComment && isScalar$1(value)) + ctx.indentAtStart = str.length + 1; + chompKeep = false; + if (!indentSeq && + indentStep.length >= 2 && + !ctx.inFlow && + !explicitKey && + isSeq(value) && + !value.flow && + !value.tag && + !value.anchor) { + // If indentSeq === false, consider '- ' as part of indentation where possible + ctx.indent = ctx.indent.substring(2); + } + let valueCommentDone = false; + const valueStr = stringify$2(value, ctx, () => (valueCommentDone = true), () => (chompKeep = true)); + let ws = ' '; + if (keyComment || vsb || vcb) { + ws = vsb ? '\n' : ''; + if (vcb) { + const cs = commentString(vcb); + ws += `\n${indentComment(cs, ctx.indent)}`; + } + if (valueStr === '' && !ctx.inFlow) { + if (ws === '\n') + ws = '\n\n'; + } + else { + ws += `\n${ctx.indent}`; + } + } + else if (!explicitKey && isCollection$1(value)) { + const vs0 = valueStr[0]; + const nl0 = valueStr.indexOf('\n'); + const hasNewline = nl0 !== -1; + const flow = ctx.inFlow ?? value.flow ?? value.items.length === 0; + if (hasNewline || !flow) { + let hasPropsLine = false; + if (hasNewline && (vs0 === '&' || vs0 === '!')) { + let sp0 = valueStr.indexOf(' '); + if (vs0 === '&' && + sp0 !== -1 && + sp0 < nl0 && + valueStr[sp0 + 1] === '!') { + sp0 = valueStr.indexOf(' ', sp0 + 1); + } + if (sp0 === -1 || nl0 < sp0) + hasPropsLine = true; + } + if (!hasPropsLine) + ws = `\n${ctx.indent}`; + } + } + else if (valueStr === '' || valueStr[0] === '\n') { + ws = ''; + } + str += ws + valueStr; + if (ctx.inFlow) { + if (valueCommentDone && onComment) + onComment(); + } + else if (valueComment && !valueCommentDone) { + str += lineComment(str, ctx.indent, commentString(valueComment)); + } + else if (chompKeep && onChompKeep) { + onChompKeep(); + } + return str; + } + + function warn(logLevel, warning) { + if (logLevel === 'debug' || logLevel === 'warn') { + if (typeof process !== 'undefined' && process.emitWarning) + process.emitWarning(warning); + else + console.warn(warning); + } + } + + const MERGE_KEY = '<<'; + function addPairToJSMap(ctx, map, { key, value }) { + if (ctx?.doc.schema.merge && isMergeKey(key)) { + value = isAlias(value) ? value.resolve(ctx.doc) : value; + if (isSeq(value)) + for (const it of value.items) + mergeToJSMap(ctx, map, it); + else if (Array.isArray(value)) + for (const it of value) + mergeToJSMap(ctx, map, it); + else + mergeToJSMap(ctx, map, value); + } + else { + const jsKey = toJS(key, '', ctx); + if (map instanceof Map) { + map.set(jsKey, toJS(value, jsKey, ctx)); + } + else if (map instanceof Set) { + map.add(jsKey); + } + else { + const stringKey = stringifyKey(key, jsKey, ctx); + const jsValue = toJS(value, stringKey, ctx); + if (stringKey in map) + Object.defineProperty(map, stringKey, { + value: jsValue, + writable: true, + enumerable: true, + configurable: true + }); + else + map[stringKey] = jsValue; + } + } + return map; + } + const isMergeKey = (key) => key === MERGE_KEY || + (isScalar$1(key) && + key.value === MERGE_KEY && + (!key.type || key.type === Scalar.PLAIN)); + // If the value associated with a merge key is a single mapping node, each of + // its key/value pairs is inserted into the current mapping, unless the key + // already exists in it. If the value associated with the merge key is a + // sequence, then this sequence is expected to contain mapping nodes and each + // of these nodes is merged in turn according to its order in the sequence. + // Keys in mapping nodes earlier in the sequence override keys specified in + // later mapping nodes. -- http://yaml.org/type/merge.html + function mergeToJSMap(ctx, map, value) { + const source = ctx && isAlias(value) ? value.resolve(ctx.doc) : value; + if (!isMap(source)) + throw new Error('Merge sources must be maps or map aliases'); + const srcMap = source.toJSON(null, ctx, Map); + for (const [key, value] of srcMap) { + if (map instanceof Map) { + if (!map.has(key)) + map.set(key, value); + } + else if (map instanceof Set) { + map.add(key); + } + else if (!Object.prototype.hasOwnProperty.call(map, key)) { + Object.defineProperty(map, key, { + value, + writable: true, + enumerable: true, + configurable: true + }); + } + } + return map; + } + function stringifyKey(key, jsKey, ctx) { + if (jsKey === null) + return ''; + if (typeof jsKey !== 'object') + return String(jsKey); + if (isNode(key) && ctx?.doc) { + const strCtx = createStringifyContext(ctx.doc, {}); + strCtx.anchors = new Set(); + for (const node of ctx.anchors.keys()) + strCtx.anchors.add(node.anchor); + strCtx.inFlow = true; + strCtx.inStringifyKey = true; + const strKey = key.toString(strCtx); + if (!ctx.mapKeyWarned) { + let jsonStr = JSON.stringify(strKey); + if (jsonStr.length > 40) + jsonStr = jsonStr.substring(0, 36) + '..."'; + warn(ctx.doc.options.logLevel, `Keys with collection values will be stringified due to JS Object restrictions: ${jsonStr}. Set mapAsMap: true to use object keys.`); + ctx.mapKeyWarned = true; + } + return strKey; + } + return JSON.stringify(jsKey); + } + + function createPair(key, value, ctx) { + const k = createNode(key, undefined, ctx); + const v = createNode(value, undefined, ctx); + return new Pair(k, v); + } + class Pair { + constructor(key, value = null) { + Object.defineProperty(this, NODE_TYPE, { value: PAIR }); + this.key = key; + this.value = value; + } + clone(schema) { + let { key, value } = this; + if (isNode(key)) + key = key.clone(schema); + if (isNode(value)) + value = value.clone(schema); + return new Pair(key, value); + } + toJSON(_, ctx) { + const pair = ctx?.mapAsMap ? new Map() : {}; + return addPairToJSMap(ctx, pair, this); + } + toString(ctx, onComment, onChompKeep) { + return ctx?.doc + ? stringifyPair(this, ctx, onComment, onChompKeep) + : JSON.stringify(this); + } + } + + function stringifyCollection(collection, ctx, options) { + const flow = ctx.inFlow ?? collection.flow; + const stringify = flow ? stringifyFlowCollection : stringifyBlockCollection; + return stringify(collection, ctx, options); + } + function stringifyBlockCollection({ comment, items }, ctx, { blockItemPrefix, flowChars, itemIndent, onChompKeep, onComment }) { + const { indent, options: { commentString } } = ctx; + const itemCtx = Object.assign({}, ctx, { indent: itemIndent, type: null }); + let chompKeep = false; // flag for the preceding node's status + const lines = []; + for (let i = 0; i < items.length; ++i) { + const item = items[i]; + let comment = null; + if (isNode(item)) { + if (!chompKeep && item.spaceBefore) + lines.push(''); + addCommentBefore(ctx, lines, item.commentBefore, chompKeep); + if (item.comment) + comment = item.comment; + } + else if (isPair(item)) { + const ik = isNode(item.key) ? item.key : null; + if (ik) { + if (!chompKeep && ik.spaceBefore) + lines.push(''); + addCommentBefore(ctx, lines, ik.commentBefore, chompKeep); + } + } + chompKeep = false; + let str = stringify$2(item, itemCtx, () => (comment = null), () => (chompKeep = true)); + if (comment) + str += lineComment(str, itemIndent, commentString(comment)); + if (chompKeep && comment) + chompKeep = false; + lines.push(blockItemPrefix + str); + } + let str; + if (lines.length === 0) { + str = flowChars.start + flowChars.end; + } + else { + str = lines[0]; + for (let i = 1; i < lines.length; ++i) { + const line = lines[i]; + str += line ? `\n${indent}${line}` : '\n'; + } + } + if (comment) { + str += '\n' + indentComment(commentString(comment), indent); + if (onComment) + onComment(); + } + else if (chompKeep && onChompKeep) + onChompKeep(); + return str; + } + function stringifyFlowCollection({ items }, ctx, { flowChars, itemIndent }) { + const { indent, indentStep, flowCollectionPadding: fcPadding, options: { commentString } } = ctx; + itemIndent += indentStep; + const itemCtx = Object.assign({}, ctx, { + indent: itemIndent, + inFlow: true, + type: null + }); + let reqNewline = false; + let linesAtValue = 0; + const lines = []; + for (let i = 0; i < items.length; ++i) { + const item = items[i]; + let comment = null; + if (isNode(item)) { + if (item.spaceBefore) + lines.push(''); + addCommentBefore(ctx, lines, item.commentBefore, false); + if (item.comment) + comment = item.comment; + } + else if (isPair(item)) { + const ik = isNode(item.key) ? item.key : null; + if (ik) { + if (ik.spaceBefore) + lines.push(''); + addCommentBefore(ctx, lines, ik.commentBefore, false); + if (ik.comment) + reqNewline = true; + } + const iv = isNode(item.value) ? item.value : null; + if (iv) { + if (iv.comment) + comment = iv.comment; + if (iv.commentBefore) + reqNewline = true; + } + else if (item.value == null && ik?.comment) { + comment = ik.comment; + } + } + if (comment) + reqNewline = true; + let str = stringify$2(item, itemCtx, () => (comment = null)); + if (i < items.length - 1) + str += ','; + if (comment) + str += lineComment(str, itemIndent, commentString(comment)); + if (!reqNewline && (lines.length > linesAtValue || str.includes('\n'))) + reqNewline = true; + lines.push(str); + linesAtValue = lines.length; + } + const { start, end } = flowChars; + if (lines.length === 0) { + return start + end; + } + else { + if (!reqNewline) { + const len = lines.reduce((sum, line) => sum + line.length + 2, 2); + reqNewline = ctx.options.lineWidth > 0 && len > ctx.options.lineWidth; + } + if (reqNewline) { + let str = start; + for (const line of lines) + str += line ? `\n${indentStep}${indent}${line}` : '\n'; + return `${str}\n${indent}${end}`; + } + else { + return `${start}${fcPadding}${lines.join(' ')}${fcPadding}${end}`; + } + } + } + function addCommentBefore({ indent, options: { commentString } }, lines, comment, chompKeep) { + if (comment && chompKeep) + comment = comment.replace(/^\n+/, ''); + if (comment) { + const ic = indentComment(commentString(comment), indent); + lines.push(ic.trimStart()); // Avoid double indent on first line + } + } + + function findPair(items, key) { + const k = isScalar$1(key) ? key.value : key; + for (const it of items) { + if (isPair(it)) { + if (it.key === key || it.key === k) + return it; + if (isScalar$1(it.key) && it.key.value === k) + return it; + } + } + return undefined; + } + class YAMLMap extends Collection { + static get tagName() { + return 'tag:yaml.org,2002:map'; + } + constructor(schema) { + super(MAP, schema); + this.items = []; + } + /** + * A generic collection parsing method that can be extended + * to other node classes that inherit from YAMLMap + */ + static from(schema, obj, ctx) { + const { keepUndefined, replacer } = ctx; + const map = new this(schema); + const add = (key, value) => { + if (typeof replacer === 'function') + value = replacer.call(obj, key, value); + else if (Array.isArray(replacer) && !replacer.includes(key)) + return; + if (value !== undefined || keepUndefined) + map.items.push(createPair(key, value, ctx)); + }; + if (obj instanceof Map) { + for (const [key, value] of obj) + add(key, value); + } + else if (obj && typeof obj === 'object') { + for (const key of Object.keys(obj)) + add(key, obj[key]); + } + if (typeof schema.sortMapEntries === 'function') { + map.items.sort(schema.sortMapEntries); + } + return map; + } + /** + * Adds a value to the collection. + * + * @param overwrite - If not set `true`, using a key that is already in the + * collection will throw. Otherwise, overwrites the previous value. + */ + add(pair, overwrite) { + let _pair; + if (isPair(pair)) + _pair = pair; + else if (!pair || typeof pair !== 'object' || !('key' in pair)) { + // In TypeScript, this never happens. + _pair = new Pair(pair, pair?.value); + } + else + _pair = new Pair(pair.key, pair.value); + const prev = findPair(this.items, _pair.key); + const sortEntries = this.schema?.sortMapEntries; + if (prev) { + if (!overwrite) + throw new Error(`Key ${_pair.key} already set`); + // For scalars, keep the old node & its comments and anchors + if (isScalar$1(prev.value) && isScalarValue(_pair.value)) + prev.value.value = _pair.value; + else + prev.value = _pair.value; + } + else if (sortEntries) { + const i = this.items.findIndex(item => sortEntries(_pair, item) < 0); + if (i === -1) + this.items.push(_pair); + else + this.items.splice(i, 0, _pair); + } + else { + this.items.push(_pair); + } + } + delete(key) { + const it = findPair(this.items, key); + if (!it) + return false; + const del = this.items.splice(this.items.indexOf(it), 1); + return del.length > 0; + } + get(key, keepScalar) { + const it = findPair(this.items, key); + const node = it?.value; + return (!keepScalar && isScalar$1(node) ? node.value : node) ?? undefined; + } + has(key) { + return !!findPair(this.items, key); + } + set(key, value) { + this.add(new Pair(key, value), true); + } + /** + * @param ctx - Conversion context, originally set in Document#toJS() + * @param {Class} Type - If set, forces the returned collection type + * @returns Instance of Type, Map, or Object + */ + toJSON(_, ctx, Type) { + const map = Type ? new Type() : ctx?.mapAsMap ? new Map() : {}; + if (ctx?.onCreate) + ctx.onCreate(map); + for (const item of this.items) + addPairToJSMap(ctx, map, item); + return map; + } + toString(ctx, onComment, onChompKeep) { + if (!ctx) + return JSON.stringify(this); + for (const item of this.items) { + if (!isPair(item)) + throw new Error(`Map items must all be pairs; found ${JSON.stringify(item)} instead`); + } + if (!ctx.allNullValues && this.hasAllNullValues(false)) + ctx = Object.assign({}, ctx, { allNullValues: true }); + return stringifyCollection(this, ctx, { + blockItemPrefix: '', + flowChars: { start: '{', end: '}' }, + itemIndent: ctx.indent || '', + onChompKeep, + onComment + }); + } + } + + const map = { + collection: 'map', + default: true, + nodeClass: YAMLMap, + tag: 'tag:yaml.org,2002:map', + resolve(map, onError) { + if (!isMap(map)) + onError('Expected a mapping for this tag'); + return map; + }, + createNode: (schema, obj, ctx) => YAMLMap.from(schema, obj, ctx) + }; + + class YAMLSeq extends Collection { + static get tagName() { + return 'tag:yaml.org,2002:seq'; + } + constructor(schema) { + super(SEQ, schema); + this.items = []; + } + add(value) { + this.items.push(value); + } + /** + * Removes a value from the collection. + * + * `key` must contain a representation of an integer for this to succeed. + * It may be wrapped in a `Scalar`. + * + * @returns `true` if the item was found and removed. + */ + delete(key) { + const idx = asItemIndex(key); + if (typeof idx !== 'number') + return false; + const del = this.items.splice(idx, 1); + return del.length > 0; + } + get(key, keepScalar) { + const idx = asItemIndex(key); + if (typeof idx !== 'number') + return undefined; + const it = this.items[idx]; + return !keepScalar && isScalar$1(it) ? it.value : it; + } + /** + * Checks if the collection includes a value with the key `key`. + * + * `key` must contain a representation of an integer for this to succeed. + * It may be wrapped in a `Scalar`. + */ + has(key) { + const idx = asItemIndex(key); + return typeof idx === 'number' && idx < this.items.length; + } + /** + * Sets a value in this collection. For `!!set`, `value` needs to be a + * boolean to add/remove the item from the set. + * + * If `key` does not contain a representation of an integer, this will throw. + * It may be wrapped in a `Scalar`. + */ + set(key, value) { + const idx = asItemIndex(key); + if (typeof idx !== 'number') + throw new Error(`Expected a valid index, not ${key}.`); + const prev = this.items[idx]; + if (isScalar$1(prev) && isScalarValue(value)) + prev.value = value; + else + this.items[idx] = value; + } + toJSON(_, ctx) { + const seq = []; + if (ctx?.onCreate) + ctx.onCreate(seq); + let i = 0; + for (const item of this.items) + seq.push(toJS(item, String(i++), ctx)); + return seq; + } + toString(ctx, onComment, onChompKeep) { + if (!ctx) + return JSON.stringify(this); + return stringifyCollection(this, ctx, { + blockItemPrefix: '- ', + flowChars: { start: '[', end: ']' }, + itemIndent: (ctx.indent || '') + ' ', + onChompKeep, + onComment + }); + } + static from(schema, obj, ctx) { + const { replacer } = ctx; + const seq = new this(schema); + if (obj && Symbol.iterator in Object(obj)) { + let i = 0; + for (let it of obj) { + if (typeof replacer === 'function') { + const key = obj instanceof Set ? it : String(i++); + it = replacer.call(obj, key, it); + } + seq.items.push(createNode(it, undefined, ctx)); + } + } + return seq; + } + } + function asItemIndex(key) { + let idx = isScalar$1(key) ? key.value : key; + if (idx && typeof idx === 'string') + idx = Number(idx); + return typeof idx === 'number' && Number.isInteger(idx) && idx >= 0 + ? idx + : null; + } + + const seq = { + collection: 'seq', + default: true, + nodeClass: YAMLSeq, + tag: 'tag:yaml.org,2002:seq', + resolve(seq, onError) { + if (!isSeq(seq)) + onError('Expected a sequence for this tag'); + return seq; + }, + createNode: (schema, obj, ctx) => YAMLSeq.from(schema, obj, ctx) + }; + + const string = { + identify: value => typeof value === 'string', + default: true, + tag: 'tag:yaml.org,2002:str', + resolve: str => str, + stringify(item, ctx, onComment, onChompKeep) { + ctx = Object.assign({ actualString: true }, ctx); + return stringifyString(item, ctx, onComment, onChompKeep); + } + }; + + const nullTag = { + identify: value => value == null, + createNode: () => new Scalar(null), + default: true, + tag: 'tag:yaml.org,2002:null', + test: /^(?:~|[Nn]ull|NULL)?$/, + resolve: () => new Scalar(null), + stringify: ({ source }, ctx) => typeof source === 'string' && nullTag.test.test(source) + ? source + : ctx.options.nullStr + }; + + const boolTag = { + identify: value => typeof value === 'boolean', + default: true, + tag: 'tag:yaml.org,2002:bool', + test: /^(?:[Tt]rue|TRUE|[Ff]alse|FALSE)$/, + resolve: str => new Scalar(str[0] === 't' || str[0] === 'T'), + stringify({ source, value }, ctx) { + if (source && boolTag.test.test(source)) { + const sv = source[0] === 't' || source[0] === 'T'; + if (value === sv) + return source; + } + return value ? ctx.options.trueStr : ctx.options.falseStr; + } + }; + + function stringifyNumber({ format, minFractionDigits, tag, value }) { + if (typeof value === 'bigint') + return String(value); + const num = typeof value === 'number' ? value : Number(value); + if (!isFinite(num)) + return isNaN(num) ? '.nan' : num < 0 ? '-.inf' : '.inf'; + let n = JSON.stringify(value); + if (!format && + minFractionDigits && + (!tag || tag === 'tag:yaml.org,2002:float') && + /^\d/.test(n)) { + let i = n.indexOf('.'); + if (i < 0) { + i = n.length; + n += '.'; + } + let d = minFractionDigits - (n.length - i - 1); + while (d-- > 0) + n += '0'; + } + return n; + } + + const floatNaN$1 = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + test: /^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/, + resolve: str => str.slice(-3).toLowerCase() === 'nan' + ? NaN + : str[0] === '-' + ? Number.NEGATIVE_INFINITY + : Number.POSITIVE_INFINITY, + stringify: stringifyNumber + }; + const floatExp$1 = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + format: 'EXP', + test: /^[-+]?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)[eE][-+]?[0-9]+$/, + resolve: str => parseFloat(str), + stringify(node) { + const num = Number(node.value); + return isFinite(num) ? num.toExponential() : stringifyNumber(node); + } + }; + const float$1 = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + test: /^[-+]?(?:\.[0-9]+|[0-9]+\.[0-9]*)$/, + resolve(str) { + const node = new Scalar(parseFloat(str)); + const dot = str.indexOf('.'); + if (dot !== -1 && str[str.length - 1] === '0') + node.minFractionDigits = str.length - dot - 1; + return node; + }, + stringify: stringifyNumber + }; + + const intIdentify$2 = (value) => typeof value === 'bigint' || Number.isInteger(value); + const intResolve$1 = (str, offset, radix, { intAsBigInt }) => (intAsBigInt ? BigInt(str) : parseInt(str.substring(offset), radix)); + function intStringify$1(node, radix, prefix) { + const { value } = node; + if (intIdentify$2(value) && value >= 0) + return prefix + value.toString(radix); + return stringifyNumber(node); + } + const intOct$1 = { + identify: value => intIdentify$2(value) && value >= 0, + default: true, + tag: 'tag:yaml.org,2002:int', + format: 'OCT', + test: /^0o[0-7]+$/, + resolve: (str, _onError, opt) => intResolve$1(str, 2, 8, opt), + stringify: node => intStringify$1(node, 8, '0o') + }; + const int$1 = { + identify: intIdentify$2, + default: true, + tag: 'tag:yaml.org,2002:int', + test: /^[-+]?[0-9]+$/, + resolve: (str, _onError, opt) => intResolve$1(str, 0, 10, opt), + stringify: stringifyNumber + }; + const intHex$1 = { + identify: value => intIdentify$2(value) && value >= 0, + default: true, + tag: 'tag:yaml.org,2002:int', + format: 'HEX', + test: /^0x[0-9a-fA-F]+$/, + resolve: (str, _onError, opt) => intResolve$1(str, 2, 16, opt), + stringify: node => intStringify$1(node, 16, '0x') + }; + + const schema$2 = [ + map, + seq, + string, + nullTag, + boolTag, + intOct$1, + int$1, + intHex$1, + floatNaN$1, + floatExp$1, + float$1 + ]; + + function intIdentify$1(value) { + return typeof value === 'bigint' || Number.isInteger(value); + } + const stringifyJSON = ({ value }) => JSON.stringify(value); + const jsonScalars = [ + { + identify: value => typeof value === 'string', + default: true, + tag: 'tag:yaml.org,2002:str', + resolve: str => str, + stringify: stringifyJSON + }, + { + identify: value => value == null, + createNode: () => new Scalar(null), + default: true, + tag: 'tag:yaml.org,2002:null', + test: /^null$/, + resolve: () => null, + stringify: stringifyJSON + }, + { + identify: value => typeof value === 'boolean', + default: true, + tag: 'tag:yaml.org,2002:bool', + test: /^true|false$/, + resolve: str => str === 'true', + stringify: stringifyJSON + }, + { + identify: intIdentify$1, + default: true, + tag: 'tag:yaml.org,2002:int', + test: /^-?(?:0|[1-9][0-9]*)$/, + resolve: (str, _onError, { intAsBigInt }) => intAsBigInt ? BigInt(str) : parseInt(str, 10), + stringify: ({ value }) => intIdentify$1(value) ? value.toString() : JSON.stringify(value) + }, + { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + test: /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]*)?(?:[eE][-+]?[0-9]+)?$/, + resolve: str => parseFloat(str), + stringify: stringifyJSON + } + ]; + const jsonError = { + default: true, + tag: '', + test: /^/, + resolve(str, onError) { + onError(`Unresolved plain scalar ${JSON.stringify(str)}`); + return str; + } + }; + const schema$1 = [map, seq].concat(jsonScalars, jsonError); + + const binary = { + identify: value => value instanceof Uint8Array, // Buffer inherits from Uint8Array + default: false, + tag: 'tag:yaml.org,2002:binary', + /** + * Returns a Buffer in node and an Uint8Array in browsers + * + * To use the resulting buffer as an image, you'll want to do something like: + * + * const blob = new Blob([buffer], { type: 'image/jpeg' }) + * document.querySelector('#photo').src = URL.createObjectURL(blob) + */ + resolve(src, onError) { + if (typeof Buffer === 'function') { + return Buffer.from(src, 'base64'); + } + else if (typeof atob === 'function') { + // On IE 11, atob() can't handle newlines + const str = atob(src.replace(/[\n\r]/g, '')); + const buffer = new Uint8Array(str.length); + for (let i = 0; i < str.length; ++i) + buffer[i] = str.charCodeAt(i); + return buffer; + } + else { + onError('This environment does not support reading binary tags; either Buffer or atob is required'); + return src; + } + }, + stringify({ comment, type, value }, ctx, onComment, onChompKeep) { + const buf = value; // checked earlier by binary.identify() + let str; + if (typeof Buffer === 'function') { + str = + buf instanceof Buffer + ? buf.toString('base64') + : Buffer.from(buf.buffer).toString('base64'); + } + else if (typeof btoa === 'function') { + let s = ''; + for (let i = 0; i < buf.length; ++i) + s += String.fromCharCode(buf[i]); + str = btoa(s); + } + else { + throw new Error('This environment does not support writing binary tags; either Buffer or btoa is required'); + } + if (!type) + type = Scalar.BLOCK_LITERAL; + if (type !== Scalar.QUOTE_DOUBLE) { + const lineWidth = Math.max(ctx.options.lineWidth - ctx.indent.length, ctx.options.minContentWidth); + const n = Math.ceil(str.length / lineWidth); + const lines = new Array(n); + for (let i = 0, o = 0; i < n; ++i, o += lineWidth) { + lines[i] = str.substr(o, lineWidth); + } + str = lines.join(type === Scalar.BLOCK_LITERAL ? '\n' : ' '); + } + return stringifyString({ comment, type, value: str }, ctx, onComment, onChompKeep); + } + }; + + function resolvePairs(seq, onError) { + if (isSeq(seq)) { + for (let i = 0; i < seq.items.length; ++i) { + let item = seq.items[i]; + if (isPair(item)) + continue; + else if (isMap(item)) { + if (item.items.length > 1) + onError('Each pair must have its own sequence indicator'); + const pair = item.items[0] || new Pair(new Scalar(null)); + if (item.commentBefore) + pair.key.commentBefore = pair.key.commentBefore + ? `${item.commentBefore}\n${pair.key.commentBefore}` + : item.commentBefore; + if (item.comment) { + const cn = pair.value ?? pair.key; + cn.comment = cn.comment + ? `${item.comment}\n${cn.comment}` + : item.comment; + } + item = pair; + } + seq.items[i] = isPair(item) ? item : new Pair(item); + } + } + else + onError('Expected a sequence for this tag'); + return seq; + } + function createPairs(schema, iterable, ctx) { + const { replacer } = ctx; + const pairs = new YAMLSeq(schema); + pairs.tag = 'tag:yaml.org,2002:pairs'; + let i = 0; + if (iterable && Symbol.iterator in Object(iterable)) + for (let it of iterable) { + if (typeof replacer === 'function') + it = replacer.call(iterable, String(i++), it); + let key, value; + if (Array.isArray(it)) { + if (it.length === 2) { + key = it[0]; + value = it[1]; + } + else + throw new TypeError(`Expected [key, value] tuple: ${it}`); + } + else if (it && it instanceof Object) { + const keys = Object.keys(it); + if (keys.length === 1) { + key = keys[0]; + value = it[key]; + } + else { + throw new TypeError(`Expected tuple with one key, not ${keys.length} keys`); + } + } + else { + key = it; + } + pairs.items.push(createPair(key, value, ctx)); + } + return pairs; + } + const pairs = { + collection: 'seq', + default: false, + tag: 'tag:yaml.org,2002:pairs', + resolve: resolvePairs, + createNode: createPairs + }; + + class YAMLOMap extends YAMLSeq { + constructor() { + super(); + this.add = YAMLMap.prototype.add.bind(this); + this.delete = YAMLMap.prototype.delete.bind(this); + this.get = YAMLMap.prototype.get.bind(this); + this.has = YAMLMap.prototype.has.bind(this); + this.set = YAMLMap.prototype.set.bind(this); + this.tag = YAMLOMap.tag; + } + /** + * If `ctx` is given, the return type is actually `Map`, + * but TypeScript won't allow widening the signature of a child method. + */ + toJSON(_, ctx) { + if (!ctx) + return super.toJSON(_); + const map = new Map(); + if (ctx?.onCreate) + ctx.onCreate(map); + for (const pair of this.items) { + let key, value; + if (isPair(pair)) { + key = toJS(pair.key, '', ctx); + value = toJS(pair.value, key, ctx); + } + else { + key = toJS(pair, '', ctx); + } + if (map.has(key)) + throw new Error('Ordered maps must not include duplicate keys'); + map.set(key, value); + } + return map; + } + static from(schema, iterable, ctx) { + const pairs = createPairs(schema, iterable, ctx); + const omap = new this(); + omap.items = pairs.items; + return omap; + } + } + YAMLOMap.tag = 'tag:yaml.org,2002:omap'; + const omap = { + collection: 'seq', + identify: value => value instanceof Map, + nodeClass: YAMLOMap, + default: false, + tag: 'tag:yaml.org,2002:omap', + resolve(seq, onError) { + const pairs = resolvePairs(seq, onError); + const seenKeys = []; + for (const { key } of pairs.items) { + if (isScalar$1(key)) { + if (seenKeys.includes(key.value)) { + onError(`Ordered maps must not include duplicate keys: ${key.value}`); + } + else { + seenKeys.push(key.value); + } + } + } + return Object.assign(new YAMLOMap(), pairs); + }, + createNode: (schema, iterable, ctx) => YAMLOMap.from(schema, iterable, ctx) + }; + + function boolStringify({ value, source }, ctx) { + const boolObj = value ? trueTag : falseTag; + if (source && boolObj.test.test(source)) + return source; + return value ? ctx.options.trueStr : ctx.options.falseStr; + } + const trueTag = { + identify: value => value === true, + default: true, + tag: 'tag:yaml.org,2002:bool', + test: /^(?:Y|y|[Yy]es|YES|[Tt]rue|TRUE|[Oo]n|ON)$/, + resolve: () => new Scalar(true), + stringify: boolStringify + }; + const falseTag = { + identify: value => value === false, + default: true, + tag: 'tag:yaml.org,2002:bool', + test: /^(?:N|n|[Nn]o|NO|[Ff]alse|FALSE|[Oo]ff|OFF)$/, + resolve: () => new Scalar(false), + stringify: boolStringify + }; + + const floatNaN = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + test: /^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/, + resolve: (str) => str.slice(-3).toLowerCase() === 'nan' + ? NaN + : str[0] === '-' + ? Number.NEGATIVE_INFINITY + : Number.POSITIVE_INFINITY, + stringify: stringifyNumber + }; + const floatExp = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + format: 'EXP', + test: /^[-+]?(?:[0-9][0-9_]*)?(?:\.[0-9_]*)?[eE][-+]?[0-9]+$/, + resolve: (str) => parseFloat(str.replace(/_/g, '')), + stringify(node) { + const num = Number(node.value); + return isFinite(num) ? num.toExponential() : stringifyNumber(node); + } + }; + const float = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + test: /^[-+]?(?:[0-9][0-9_]*)?\.[0-9_]*$/, + resolve(str) { + const node = new Scalar(parseFloat(str.replace(/_/g, ''))); + const dot = str.indexOf('.'); + if (dot !== -1) { + const f = str.substring(dot + 1).replace(/_/g, ''); + if (f[f.length - 1] === '0') + node.minFractionDigits = f.length; + } + return node; + }, + stringify: stringifyNumber + }; + + const intIdentify = (value) => typeof value === 'bigint' || Number.isInteger(value); + function intResolve(str, offset, radix, { intAsBigInt }) { + const sign = str[0]; + if (sign === '-' || sign === '+') + offset += 1; + str = str.substring(offset).replace(/_/g, ''); + if (intAsBigInt) { + switch (radix) { + case 2: + str = `0b${str}`; + break; + case 8: + str = `0o${str}`; + break; + case 16: + str = `0x${str}`; + break; + } + const n = BigInt(str); + return sign === '-' ? BigInt(-1) * n : n; + } + const n = parseInt(str, radix); + return sign === '-' ? -1 * n : n; + } + function intStringify(node, radix, prefix) { + const { value } = node; + if (intIdentify(value)) { + const str = value.toString(radix); + return value < 0 ? '-' + prefix + str.substr(1) : prefix + str; + } + return stringifyNumber(node); + } + const intBin = { + identify: intIdentify, + default: true, + tag: 'tag:yaml.org,2002:int', + format: 'BIN', + test: /^[-+]?0b[0-1_]+$/, + resolve: (str, _onError, opt) => intResolve(str, 2, 2, opt), + stringify: node => intStringify(node, 2, '0b') + }; + const intOct = { + identify: intIdentify, + default: true, + tag: 'tag:yaml.org,2002:int', + format: 'OCT', + test: /^[-+]?0[0-7_]+$/, + resolve: (str, _onError, opt) => intResolve(str, 1, 8, opt), + stringify: node => intStringify(node, 8, '0') + }; + const int = { + identify: intIdentify, + default: true, + tag: 'tag:yaml.org,2002:int', + test: /^[-+]?[0-9][0-9_]*$/, + resolve: (str, _onError, opt) => intResolve(str, 0, 10, opt), + stringify: stringifyNumber + }; + const intHex = { + identify: intIdentify, + default: true, + tag: 'tag:yaml.org,2002:int', + format: 'HEX', + test: /^[-+]?0x[0-9a-fA-F_]+$/, + resolve: (str, _onError, opt) => intResolve(str, 2, 16, opt), + stringify: node => intStringify(node, 16, '0x') + }; + + class YAMLSet extends YAMLMap { + constructor(schema) { + super(schema); + this.tag = YAMLSet.tag; + } + add(key) { + let pair; + if (isPair(key)) + pair = key; + else if (key && + typeof key === 'object' && + 'key' in key && + 'value' in key && + key.value === null) + pair = new Pair(key.key, null); + else + pair = new Pair(key, null); + const prev = findPair(this.items, pair.key); + if (!prev) + this.items.push(pair); + } + /** + * If `keepPair` is `true`, returns the Pair matching `key`. + * Otherwise, returns the value of that Pair's key. + */ + get(key, keepPair) { + const pair = findPair(this.items, key); + return !keepPair && isPair(pair) + ? isScalar$1(pair.key) + ? pair.key.value + : pair.key + : pair; + } + set(key, value) { + if (typeof value !== 'boolean') + throw new Error(`Expected boolean value for set(key, value) in a YAML set, not ${typeof value}`); + const prev = findPair(this.items, key); + if (prev && !value) { + this.items.splice(this.items.indexOf(prev), 1); + } + else if (!prev && value) { + this.items.push(new Pair(key)); + } + } + toJSON(_, ctx) { + return super.toJSON(_, ctx, Set); + } + toString(ctx, onComment, onChompKeep) { + if (!ctx) + return JSON.stringify(this); + if (this.hasAllNullValues(true)) + return super.toString(Object.assign({}, ctx, { allNullValues: true }), onComment, onChompKeep); + else + throw new Error('Set items must all have null values'); + } + static from(schema, iterable, ctx) { + const { replacer } = ctx; + const set = new this(schema); + if (iterable && Symbol.iterator in Object(iterable)) + for (let value of iterable) { + if (typeof replacer === 'function') + value = replacer.call(iterable, value, value); + set.items.push(createPair(value, null, ctx)); + } + return set; + } + } + YAMLSet.tag = 'tag:yaml.org,2002:set'; + const set = { + collection: 'map', + identify: value => value instanceof Set, + nodeClass: YAMLSet, + default: false, + tag: 'tag:yaml.org,2002:set', + createNode: (schema, iterable, ctx) => YAMLSet.from(schema, iterable, ctx), + resolve(map, onError) { + if (isMap(map)) { + if (map.hasAllNullValues(true)) + return Object.assign(new YAMLSet(), map); + else + onError('Set items must all have null values'); + } + else + onError('Expected a mapping for this tag'); + return map; + } + }; + + /** Internal types handle bigint as number, because TS can't figure it out. */ + function parseSexagesimal(str, asBigInt) { + const sign = str[0]; + const parts = sign === '-' || sign === '+' ? str.substring(1) : str; + const num = (n) => asBigInt ? BigInt(n) : Number(n); + const res = parts + .replace(/_/g, '') + .split(':') + .reduce((res, p) => res * num(60) + num(p), num(0)); + return (sign === '-' ? num(-1) * res : res); + } + /** + * hhhh:mm:ss.sss + * + * Internal types handle bigint as number, because TS can't figure it out. + */ + function stringifySexagesimal(node) { + let { value } = node; + let num = (n) => n; + if (typeof value === 'bigint') + num = n => BigInt(n); + else if (isNaN(value) || !isFinite(value)) + return stringifyNumber(node); + let sign = ''; + if (value < 0) { + sign = '-'; + value *= num(-1); + } + const _60 = num(60); + const parts = [value % _60]; // seconds, including ms + if (value < 60) { + parts.unshift(0); // at least one : is required + } + else { + value = (value - parts[0]) / _60; + parts.unshift(value % _60); // minutes + if (value >= 60) { + value = (value - parts[0]) / _60; + parts.unshift(value); // hours + } + } + return (sign + + parts + .map(n => String(n).padStart(2, '0')) + .join(':') + .replace(/000000\d*$/, '') // % 60 may introduce error + ); + } + const intTime = { + identify: value => typeof value === 'bigint' || Number.isInteger(value), + default: true, + tag: 'tag:yaml.org,2002:int', + format: 'TIME', + test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+$/, + resolve: (str, _onError, { intAsBigInt }) => parseSexagesimal(str, intAsBigInt), + stringify: stringifySexagesimal + }; + const floatTime = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + format: 'TIME', + test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*$/, + resolve: str => parseSexagesimal(str, false), + stringify: stringifySexagesimal + }; + const timestamp = { + identify: value => value instanceof Date, + default: true, + tag: 'tag:yaml.org,2002:timestamp', + // If the time zone is omitted, the timestamp is assumed to be specified in UTC. The time part + // may be omitted altogether, resulting in a date format. In such a case, the time part is + // assumed to be 00:00:00Z (start of day, UTC). + test: RegExp('^([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})' + // YYYY-Mm-Dd + '(?:' + // time is optional + '(?:t|T|[ \\t]+)' + // t | T | whitespace + '([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}(\\.[0-9]+)?)' + // Hh:Mm:Ss(.ss)? + '(?:[ \\t]*(Z|[-+][012]?[0-9](?::[0-9]{2})?))?' + // Z | +5 | -03:30 + ')?$'), + resolve(str) { + const match = str.match(timestamp.test); + if (!match) + throw new Error('!!timestamp expects a date, starting with yyyy-mm-dd'); + const [, year, month, day, hour, minute, second] = match.map(Number); + const millisec = match[7] ? Number((match[7] + '00').substr(1, 3)) : 0; + let date = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec); + const tz = match[8]; + if (tz && tz !== 'Z') { + let d = parseSexagesimal(tz, false); + if (Math.abs(d) < 30) + d *= 60; + date -= 60000 * d; + } + return new Date(date); + }, + stringify: ({ value }) => value.toISOString().replace(/((T00:00)?:00)?\.000Z$/, '') + }; + + const schema = [ + map, + seq, + string, + nullTag, + trueTag, + falseTag, + intBin, + intOct, + int, + intHex, + floatNaN, + floatExp, + float, + binary, + omap, + pairs, + set, + intTime, + floatTime, + timestamp + ]; + + const schemas = new Map([ + ['core', schema$2], + ['failsafe', [map, seq, string]], + ['json', schema$1], + ['yaml11', schema], + ['yaml-1.1', schema] + ]); + const tagsByName = { + binary, + bool: boolTag, + float: float$1, + floatExp: floatExp$1, + floatNaN: floatNaN$1, + floatTime, + int: int$1, + intHex: intHex$1, + intOct: intOct$1, + intTime, + map, + null: nullTag, + omap, + pairs, + seq, + set, + timestamp + }; + const coreKnownTags = { + 'tag:yaml.org,2002:binary': binary, + 'tag:yaml.org,2002:omap': omap, + 'tag:yaml.org,2002:pairs': pairs, + 'tag:yaml.org,2002:set': set, + 'tag:yaml.org,2002:timestamp': timestamp + }; + function getTags(customTags, schemaName) { + let tags = schemas.get(schemaName); + if (!tags) { + if (Array.isArray(customTags)) + tags = []; + else { + const keys = Array.from(schemas.keys()) + .filter(key => key !== 'yaml11') + .map(key => JSON.stringify(key)) + .join(', '); + throw new Error(`Unknown schema "${schemaName}"; use one of ${keys} or define customTags array`); + } + } + if (Array.isArray(customTags)) { + for (const tag of customTags) + tags = tags.concat(tag); + } + else if (typeof customTags === 'function') { + tags = customTags(tags.slice()); + } + return tags.map(tag => { + if (typeof tag !== 'string') + return tag; + const tagObj = tagsByName[tag]; + if (tagObj) + return tagObj; + const keys = Object.keys(tagsByName) + .map(key => JSON.stringify(key)) + .join(', '); + throw new Error(`Unknown custom tag "${tag}"; use one of ${keys}`); + }); + } + + const sortMapEntriesByKey = (a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0; + class Schema { + constructor({ compat, customTags, merge, resolveKnownTags, schema, sortMapEntries, toStringDefaults }) { + this.compat = Array.isArray(compat) + ? getTags(compat, 'compat') + : compat + ? getTags(null, compat) + : null; + this.merge = !!merge; + this.name = (typeof schema === 'string' && schema) || 'core'; + this.knownTags = resolveKnownTags ? coreKnownTags : {}; + this.tags = getTags(customTags, this.name); + this.toStringOptions = toStringDefaults ?? null; + Object.defineProperty(this, MAP, { value: map }); + Object.defineProperty(this, SCALAR$1, { value: string }); + Object.defineProperty(this, SEQ, { value: seq }); + // Used by createMap() + this.sortMapEntries = + typeof sortMapEntries === 'function' + ? sortMapEntries + : sortMapEntries === true + ? sortMapEntriesByKey + : null; + } + clone() { + const copy = Object.create(Schema.prototype, Object.getOwnPropertyDescriptors(this)); + copy.tags = this.tags.slice(); + return copy; + } + } + + function stringifyDocument(doc, options) { + const lines = []; + let hasDirectives = options.directives === true; + if (options.directives !== false && doc.directives) { + const dir = doc.directives.toString(doc); + if (dir) { + lines.push(dir); + hasDirectives = true; + } + else if (doc.directives.docStart) + hasDirectives = true; + } + if (hasDirectives) + lines.push('---'); + const ctx = createStringifyContext(doc, options); + const { commentString } = ctx.options; + if (doc.commentBefore) { + if (lines.length !== 1) + lines.unshift(''); + const cs = commentString(doc.commentBefore); + lines.unshift(indentComment(cs, '')); + } + let chompKeep = false; + let contentComment = null; + if (doc.contents) { + if (isNode(doc.contents)) { + if (doc.contents.spaceBefore && hasDirectives) + lines.push(''); + if (doc.contents.commentBefore) { + const cs = commentString(doc.contents.commentBefore); + lines.push(indentComment(cs, '')); + } + // top-level block scalars need to be indented if followed by a comment + ctx.forceBlockIndent = !!doc.comment; + contentComment = doc.contents.comment; + } + const onChompKeep = contentComment ? undefined : () => (chompKeep = true); + let body = stringify$2(doc.contents, ctx, () => (contentComment = null), onChompKeep); + if (contentComment) + body += lineComment(body, '', commentString(contentComment)); + if ((body[0] === '|' || body[0] === '>') && + lines[lines.length - 1] === '---') { + // Top-level block scalars with a preceding doc marker ought to use the + // same line for their header. + lines[lines.length - 1] = `--- ${body}`; + } + else + lines.push(body); + } + else { + lines.push(stringify$2(doc.contents, ctx)); + } + if (doc.directives?.docEnd) { + if (doc.comment) { + const cs = commentString(doc.comment); + if (cs.includes('\n')) { + lines.push('...'); + lines.push(indentComment(cs, '')); + } + else { + lines.push(`... ${cs}`); + } + } + else { + lines.push('...'); + } + } + else { + let dc = doc.comment; + if (dc && chompKeep) + dc = dc.replace(/^\n+/, ''); + if (dc) { + if ((!chompKeep || contentComment) && lines[lines.length - 1] !== '') + lines.push(''); + lines.push(indentComment(commentString(dc), '')); + } + } + return lines.join('\n') + '\n'; + } + + class Document { + constructor(value, replacer, options) { + /** A comment before this Document */ + this.commentBefore = null; + /** A comment immediately after this Document */ + this.comment = null; + /** Errors encountered during parsing. */ + this.errors = []; + /** Warnings encountered during parsing. */ + this.warnings = []; + Object.defineProperty(this, NODE_TYPE, { value: DOC }); + let _replacer = null; + if (typeof replacer === 'function' || Array.isArray(replacer)) { + _replacer = replacer; + } + else if (options === undefined && replacer) { + options = replacer; + replacer = undefined; + } + const opt = Object.assign({ + intAsBigInt: false, + keepSourceTokens: false, + logLevel: 'warn', + prettyErrors: true, + strict: true, + uniqueKeys: true, + version: '1.2' + }, options); + this.options = opt; + let { version } = opt; + if (options?._directives) { + this.directives = options._directives.atDocument(); + if (this.directives.yaml.explicit) + version = this.directives.yaml.version; + } + else + this.directives = new Directives({ version }); + this.setSchema(version, options); + // @ts-expect-error We can't really know that this matches Contents. + this.contents = + value === undefined ? null : this.createNode(value, _replacer, options); + } + /** + * Create a deep copy of this Document and its contents. + * + * Custom Node values that inherit from `Object` still refer to their original instances. + */ + clone() { + const copy = Object.create(Document.prototype, { + [NODE_TYPE]: { value: DOC } + }); + copy.commentBefore = this.commentBefore; + copy.comment = this.comment; + copy.errors = this.errors.slice(); + copy.warnings = this.warnings.slice(); + copy.options = Object.assign({}, this.options); + if (this.directives) + copy.directives = this.directives.clone(); + copy.schema = this.schema.clone(); + // @ts-expect-error We can't really know that this matches Contents. + copy.contents = isNode(this.contents) + ? this.contents.clone(copy.schema) + : this.contents; + if (this.range) + copy.range = this.range.slice(); + return copy; + } + /** Adds a value to the document. */ + add(value) { + if (assertCollection(this.contents)) + this.contents.add(value); + } + /** Adds a value to the document. */ + addIn(path, value) { + if (assertCollection(this.contents)) + this.contents.addIn(path, value); + } + /** + * Create a new `Alias` node, ensuring that the target `node` has the required anchor. + * + * If `node` already has an anchor, `name` is ignored. + * Otherwise, the `node.anchor` value will be set to `name`, + * or if an anchor with that name is already present in the document, + * `name` will be used as a prefix for a new unique anchor. + * If `name` is undefined, the generated anchor will use 'a' as a prefix. + */ + createAlias(node, name) { + if (!node.anchor) { + const prev = anchorNames(this); + node.anchor = + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + !name || prev.has(name) ? findNewAnchor(name || 'a', prev) : name; + } + return new Alias(node.anchor); + } + createNode(value, replacer, options) { + let _replacer = undefined; + if (typeof replacer === 'function') { + value = replacer.call({ '': value }, '', value); + _replacer = replacer; + } + else if (Array.isArray(replacer)) { + const keyToStr = (v) => typeof v === 'number' || v instanceof String || v instanceof Number; + const asStr = replacer.filter(keyToStr).map(String); + if (asStr.length > 0) + replacer = replacer.concat(asStr); + _replacer = replacer; + } + else if (options === undefined && replacer) { + options = replacer; + replacer = undefined; + } + const { aliasDuplicateObjects, anchorPrefix, flow, keepUndefined, onTagObj, tag } = options ?? {}; + const { onAnchor, setAnchors, sourceObjects } = createNodeAnchors(this, + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + anchorPrefix || 'a'); + const ctx = { + aliasDuplicateObjects: aliasDuplicateObjects ?? true, + keepUndefined: keepUndefined ?? false, + onAnchor, + onTagObj, + replacer: _replacer, + schema: this.schema, + sourceObjects + }; + const node = createNode(value, tag, ctx); + if (flow && isCollection$1(node)) + node.flow = true; + setAnchors(); + return node; + } + /** + * Convert a key and a value into a `Pair` using the current schema, + * recursively wrapping all values as `Scalar` or `Collection` nodes. + */ + createPair(key, value, options = {}) { + const k = this.createNode(key, null, options); + const v = this.createNode(value, null, options); + return new Pair(k, v); + } + /** + * Removes a value from the document. + * @returns `true` if the item was found and removed. + */ + delete(key) { + return assertCollection(this.contents) ? this.contents.delete(key) : false; + } + /** + * Removes a value from the document. + * @returns `true` if the item was found and removed. + */ + deleteIn(path) { + if (isEmptyPath(path)) { + if (this.contents == null) + return false; + // @ts-expect-error Presumed impossible if Strict extends false + this.contents = null; + return true; + } + return assertCollection(this.contents) + ? this.contents.deleteIn(path) + : false; + } + /** + * Returns item at `key`, or `undefined` if not found. By default unwraps + * scalar values from their surrounding node; to disable set `keepScalar` to + * `true` (collections are always returned intact). + */ + get(key, keepScalar) { + return isCollection$1(this.contents) + ? this.contents.get(key, keepScalar) + : undefined; + } + /** + * Returns item at `path`, or `undefined` if not found. By default unwraps + * scalar values from their surrounding node; to disable set `keepScalar` to + * `true` (collections are always returned intact). + */ + getIn(path, keepScalar) { + if (isEmptyPath(path)) + return !keepScalar && isScalar$1(this.contents) + ? this.contents.value + : this.contents; + return isCollection$1(this.contents) + ? this.contents.getIn(path, keepScalar) + : undefined; + } + /** + * Checks if the document includes a value with the key `key`. + */ + has(key) { + return isCollection$1(this.contents) ? this.contents.has(key) : false; + } + /** + * Checks if the document includes a value at `path`. + */ + hasIn(path) { + if (isEmptyPath(path)) + return this.contents !== undefined; + return isCollection$1(this.contents) ? this.contents.hasIn(path) : false; + } + /** + * Sets a value in this document. For `!!set`, `value` needs to be a + * boolean to add/remove the item from the set. + */ + set(key, value) { + if (this.contents == null) { + // @ts-expect-error We can't really know that this matches Contents. + this.contents = collectionFromPath(this.schema, [key], value); + } + else if (assertCollection(this.contents)) { + this.contents.set(key, value); + } + } + /** + * Sets a value in this document. For `!!set`, `value` needs to be a + * boolean to add/remove the item from the set. + */ + setIn(path, value) { + if (isEmptyPath(path)) { + // @ts-expect-error We can't really know that this matches Contents. + this.contents = value; + } + else if (this.contents == null) { + // @ts-expect-error We can't really know that this matches Contents. + this.contents = collectionFromPath(this.schema, Array.from(path), value); + } + else if (assertCollection(this.contents)) { + this.contents.setIn(path, value); + } + } + /** + * Change the YAML version and schema used by the document. + * A `null` version disables support for directives, explicit tags, anchors, and aliases. + * It also requires the `schema` option to be given as a `Schema` instance value. + * + * Overrides all previously set schema options. + */ + setSchema(version, options = {}) { + if (typeof version === 'number') + version = String(version); + let opt; + switch (version) { + case '1.1': + if (this.directives) + this.directives.yaml.version = '1.1'; + else + this.directives = new Directives({ version: '1.1' }); + opt = { merge: true, resolveKnownTags: false, schema: 'yaml-1.1' }; + break; + case '1.2': + case 'next': + if (this.directives) + this.directives.yaml.version = version; + else + this.directives = new Directives({ version }); + opt = { merge: false, resolveKnownTags: true, schema: 'core' }; + break; + case null: + if (this.directives) + delete this.directives; + opt = null; + break; + default: { + const sv = JSON.stringify(version); + throw new Error(`Expected '1.1', '1.2' or null as first argument, but found: ${sv}`); + } + } + // Not using `instanceof Schema` to allow for duck typing + if (options.schema instanceof Object) + this.schema = options.schema; + else if (opt) + this.schema = new Schema(Object.assign(opt, options)); + else + throw new Error(`With a null YAML version, the { schema: Schema } option is required`); + } + // json & jsonArg are only used from toJSON() + toJS({ json, jsonArg, mapAsMap, maxAliasCount, onAnchor, reviver } = {}) { + const ctx = { + anchors: new Map(), + doc: this, + keep: !json, + mapAsMap: mapAsMap === true, + mapKeyWarned: false, + maxAliasCount: typeof maxAliasCount === 'number' ? maxAliasCount : 100 + }; + const res = toJS(this.contents, jsonArg ?? '', ctx); + if (typeof onAnchor === 'function') + for (const { count, res } of ctx.anchors.values()) + onAnchor(res, count); + return typeof reviver === 'function' + ? applyReviver(reviver, { '': res }, '', res) + : res; + } + /** + * A JSON representation of the document `contents`. + * + * @param jsonArg Used by `JSON.stringify` to indicate the array index or + * property name. + */ + toJSON(jsonArg, onAnchor) { + return this.toJS({ json: true, jsonArg, mapAsMap: false, onAnchor }); + } + /** A YAML representation of the document. */ + toString(options = {}) { + if (this.errors.length > 0) + throw new Error('Document with errors cannot be stringified'); + if ('indent' in options && + (!Number.isInteger(options.indent) || Number(options.indent) <= 0)) { + const s = JSON.stringify(options.indent); + throw new Error(`"indent" option must be a positive integer, not ${s}`); + } + return stringifyDocument(this, options); + } + } + function assertCollection(contents) { + if (isCollection$1(contents)) + return true; + throw new Error('Expected a YAML collection as document contents'); + } + + class YAMLError extends Error { + constructor(name, pos, code, message) { + super(); + this.name = name; + this.code = code; + this.message = message; + this.pos = pos; + } + } + class YAMLParseError extends YAMLError { + constructor(pos, code, message) { + super('YAMLParseError', pos, code, message); + } + } + class YAMLWarning extends YAMLError { + constructor(pos, code, message) { + super('YAMLWarning', pos, code, message); + } + } + const prettifyError = (src, lc) => (error) => { + if (error.pos[0] === -1) + return; + error.linePos = error.pos.map(pos => lc.linePos(pos)); + const { line, col } = error.linePos[0]; + error.message += ` at line ${line}, column ${col}`; + let ci = col - 1; + let lineStr = src + .substring(lc.lineStarts[line - 1], lc.lineStarts[line]) + .replace(/[\n\r]+$/, ''); + // Trim to max 80 chars, keeping col position near the middle + if (ci >= 60 && lineStr.length > 80) { + const trimStart = Math.min(ci - 39, lineStr.length - 79); + lineStr = '…' + lineStr.substring(trimStart); + ci -= trimStart - 1; + } + if (lineStr.length > 80) + lineStr = lineStr.substring(0, 79) + '…'; + // Include previous line in context if pointing at line start + if (line > 1 && /^ *$/.test(lineStr.substring(0, ci))) { + // Regexp won't match if start is trimmed + let prev = src.substring(lc.lineStarts[line - 2], lc.lineStarts[line - 1]); + if (prev.length > 80) + prev = prev.substring(0, 79) + '…\n'; + lineStr = prev + lineStr; + } + if (/[^ ]/.test(lineStr)) { + let count = 1; + const end = error.linePos[1]; + if (end && end.line === line && end.col > col) { + count = Math.max(1, Math.min(end.col - col, 80 - ci)); + } + const pointer = ' '.repeat(ci) + '^'.repeat(count); + error.message += `:\n\n${lineStr}\n${pointer}\n`; + } + }; + + function resolveProps(tokens, { flow, indicator, next, offset, onError, parentIndent, startOnNewline }) { + let spaceBefore = false; + let atNewline = startOnNewline; + let hasSpace = startOnNewline; + let comment = ''; + let commentSep = ''; + let hasNewline = false; + let reqSpace = false; + let tab = null; + let anchor = null; + let tag = null; + let newlineAfterProp = null; + let comma = null; + let found = null; + let start = null; + for (const token of tokens) { + if (reqSpace) { + if (token.type !== 'space' && + token.type !== 'newline' && + token.type !== 'comma') + onError(token.offset, 'MISSING_CHAR', 'Tags and anchors must be separated from the next token by white space'); + reqSpace = false; + } + if (tab) { + if (atNewline && token.type !== 'comment' && token.type !== 'newline') { + onError(tab, 'TAB_AS_INDENT', 'Tabs are not allowed as indentation'); + } + tab = null; + } + switch (token.type) { + case 'space': + // At the doc level, tabs at line start may be parsed + // as leading white space rather than indentation. + // In a flow collection, only the parser handles indent. + if (!flow && + (indicator !== 'doc-start' || next?.type !== 'flow-collection') && + token.source.includes('\t')) { + tab = token; + } + hasSpace = true; + break; + case 'comment': { + if (!hasSpace) + onError(token, 'MISSING_CHAR', 'Comments must be separated from other tokens by white space characters'); + const cb = token.source.substring(1) || ' '; + if (!comment) + comment = cb; + else + comment += commentSep + cb; + commentSep = ''; + atNewline = false; + break; + } + case 'newline': + if (atNewline) { + if (comment) + comment += token.source; + else + spaceBefore = true; + } + else + commentSep += token.source; + atNewline = true; + hasNewline = true; + if (anchor || tag) + newlineAfterProp = token; + hasSpace = true; + break; + case 'anchor': + if (anchor) + onError(token, 'MULTIPLE_ANCHORS', 'A node can have at most one anchor'); + if (token.source.endsWith(':')) + onError(token.offset + token.source.length - 1, 'BAD_ALIAS', 'Anchor ending in : is ambiguous', true); + anchor = token; + if (start === null) + start = token.offset; + atNewline = false; + hasSpace = false; + reqSpace = true; + break; + case 'tag': { + if (tag) + onError(token, 'MULTIPLE_TAGS', 'A node can have at most one tag'); + tag = token; + if (start === null) + start = token.offset; + atNewline = false; + hasSpace = false; + reqSpace = true; + break; + } + case indicator: + // Could here handle preceding comments differently + if (anchor || tag) + onError(token, 'BAD_PROP_ORDER', `Anchors and tags must be after the ${token.source} indicator`); + if (found) + onError(token, 'UNEXPECTED_TOKEN', `Unexpected ${token.source} in ${flow ?? 'collection'}`); + found = token; + atNewline = + indicator === 'seq-item-ind' || indicator === 'explicit-key-ind'; + hasSpace = false; + break; + case 'comma': + if (flow) { + if (comma) + onError(token, 'UNEXPECTED_TOKEN', `Unexpected , in ${flow}`); + comma = token; + atNewline = false; + hasSpace = false; + break; + } + // else fallthrough + default: + onError(token, 'UNEXPECTED_TOKEN', `Unexpected ${token.type} token`); + atNewline = false; + hasSpace = false; + } + } + const last = tokens[tokens.length - 1]; + const end = last ? last.offset + last.source.length : offset; + if (reqSpace && + next && + next.type !== 'space' && + next.type !== 'newline' && + next.type !== 'comma' && + (next.type !== 'scalar' || next.source !== '')) { + onError(next.offset, 'MISSING_CHAR', 'Tags and anchors must be separated from the next token by white space'); + } + if (tab && + ((atNewline && tab.indent <= parentIndent) || + next?.type === 'block-map' || + next?.type === 'block-seq')) + onError(tab, 'TAB_AS_INDENT', 'Tabs are not allowed as indentation'); + return { + comma, + found, + spaceBefore, + comment, + hasNewline, + anchor, + tag, + newlineAfterProp, + end, + start: start ?? end + }; + } + + function containsNewline(key) { + if (!key) + return null; + switch (key.type) { + case 'alias': + case 'scalar': + case 'double-quoted-scalar': + case 'single-quoted-scalar': + if (key.source.includes('\n')) + return true; + if (key.end) + for (const st of key.end) + if (st.type === 'newline') + return true; + return false; + case 'flow-collection': + for (const it of key.items) { + for (const st of it.start) + if (st.type === 'newline') + return true; + if (it.sep) + for (const st of it.sep) + if (st.type === 'newline') + return true; + if (containsNewline(it.key) || containsNewline(it.value)) + return true; + } + return false; + default: + return true; + } + } + + function flowIndentCheck(indent, fc, onError) { + if (fc?.type === 'flow-collection') { + const end = fc.end[0]; + if (end.indent === indent && + (end.source === ']' || end.source === '}') && + containsNewline(fc)) { + const msg = 'Flow end indicator should be more indented than parent'; + onError(end, 'BAD_INDENT', msg, true); + } + } + } + + function mapIncludes(ctx, items, search) { + const { uniqueKeys } = ctx.options; + if (uniqueKeys === false) + return false; + const isEqual = typeof uniqueKeys === 'function' + ? uniqueKeys + : (a, b) => a === b || + (isScalar$1(a) && + isScalar$1(b) && + a.value === b.value && + !(a.value === '<<' && ctx.schema.merge)); + return items.some(pair => isEqual(pair.key, search)); + } + + const startColMsg = 'All mapping items must start at the same column'; + function resolveBlockMap({ composeNode, composeEmptyNode }, ctx, bm, onError, tag) { + const NodeClass = tag?.nodeClass ?? YAMLMap; + const map = new NodeClass(ctx.schema); + if (ctx.atRoot) + ctx.atRoot = false; + let offset = bm.offset; + let commentEnd = null; + for (const collItem of bm.items) { + const { start, key, sep, value } = collItem; + // key properties + const keyProps = resolveProps(start, { + indicator: 'explicit-key-ind', + next: key ?? sep?.[0], + offset, + onError, + parentIndent: bm.indent, + startOnNewline: true + }); + const implicitKey = !keyProps.found; + if (implicitKey) { + if (key) { + if (key.type === 'block-seq') + onError(offset, 'BLOCK_AS_IMPLICIT_KEY', 'A block sequence may not be used as an implicit map key'); + else if ('indent' in key && key.indent !== bm.indent) + onError(offset, 'BAD_INDENT', startColMsg); + } + if (!keyProps.anchor && !keyProps.tag && !sep) { + commentEnd = keyProps.end; + if (keyProps.comment) { + if (map.comment) + map.comment += '\n' + keyProps.comment; + else + map.comment = keyProps.comment; + } + continue; + } + if (keyProps.newlineAfterProp || containsNewline(key)) { + onError(key ?? start[start.length - 1], 'MULTILINE_IMPLICIT_KEY', 'Implicit keys need to be on a single line'); + } + } + else if (keyProps.found?.indent !== bm.indent) { + onError(offset, 'BAD_INDENT', startColMsg); + } + // key value + const keyStart = keyProps.end; + const keyNode = key + ? composeNode(ctx, key, keyProps, onError) + : composeEmptyNode(ctx, keyStart, start, null, keyProps, onError); + if (ctx.schema.compat) + flowIndentCheck(bm.indent, key, onError); + if (mapIncludes(ctx, map.items, keyNode)) + onError(keyStart, 'DUPLICATE_KEY', 'Map keys must be unique'); + // value properties + const valueProps = resolveProps(sep ?? [], { + indicator: 'map-value-ind', + next: value, + offset: keyNode.range[2], + onError, + parentIndent: bm.indent, + startOnNewline: !key || key.type === 'block-scalar' + }); + offset = valueProps.end; + if (valueProps.found) { + if (implicitKey) { + if (value?.type === 'block-map' && !valueProps.hasNewline) + onError(offset, 'BLOCK_AS_IMPLICIT_KEY', 'Nested mappings are not allowed in compact mappings'); + if (ctx.options.strict && + keyProps.start < valueProps.found.offset - 1024) + onError(keyNode.range, 'KEY_OVER_1024_CHARS', 'The : indicator must be at most 1024 chars after the start of an implicit block mapping key'); + } + // value value + const valueNode = value + ? composeNode(ctx, value, valueProps, onError) + : composeEmptyNode(ctx, offset, sep, null, valueProps, onError); + if (ctx.schema.compat) + flowIndentCheck(bm.indent, value, onError); + offset = valueNode.range[2]; + const pair = new Pair(keyNode, valueNode); + if (ctx.options.keepSourceTokens) + pair.srcToken = collItem; + map.items.push(pair); + } + else { + // key with no value + if (implicitKey) + onError(keyNode.range, 'MISSING_CHAR', 'Implicit map keys need to be followed by map values'); + if (valueProps.comment) { + if (keyNode.comment) + keyNode.comment += '\n' + valueProps.comment; + else + keyNode.comment = valueProps.comment; + } + const pair = new Pair(keyNode); + if (ctx.options.keepSourceTokens) + pair.srcToken = collItem; + map.items.push(pair); + } + } + if (commentEnd && commentEnd < offset) + onError(commentEnd, 'IMPOSSIBLE', 'Map comment with trailing content'); + map.range = [bm.offset, offset, commentEnd ?? offset]; + return map; + } + + function resolveBlockSeq({ composeNode, composeEmptyNode }, ctx, bs, onError, tag) { + const NodeClass = tag?.nodeClass ?? YAMLSeq; + const seq = new NodeClass(ctx.schema); + if (ctx.atRoot) + ctx.atRoot = false; + let offset = bs.offset; + let commentEnd = null; + for (const { start, value } of bs.items) { + const props = resolveProps(start, { + indicator: 'seq-item-ind', + next: value, + offset, + onError, + parentIndent: bs.indent, + startOnNewline: true + }); + if (!props.found) { + if (props.anchor || props.tag || value) { + if (value && value.type === 'block-seq') + onError(props.end, 'BAD_INDENT', 'All sequence items must start at the same column'); + else + onError(offset, 'MISSING_CHAR', 'Sequence item without - indicator'); + } + else { + commentEnd = props.end; + if (props.comment) + seq.comment = props.comment; + continue; + } + } + const node = value + ? composeNode(ctx, value, props, onError) + : composeEmptyNode(ctx, props.end, start, null, props, onError); + if (ctx.schema.compat) + flowIndentCheck(bs.indent, value, onError); + offset = node.range[2]; + seq.items.push(node); + } + seq.range = [bs.offset, offset, commentEnd ?? offset]; + return seq; + } + + function resolveEnd(end, offset, reqSpace, onError) { + let comment = ''; + if (end) { + let hasSpace = false; + let sep = ''; + for (const token of end) { + const { source, type } = token; + switch (type) { + case 'space': + hasSpace = true; + break; + case 'comment': { + if (reqSpace && !hasSpace) + onError(token, 'MISSING_CHAR', 'Comments must be separated from other tokens by white space characters'); + const cb = source.substring(1) || ' '; + if (!comment) + comment = cb; + else + comment += sep + cb; + sep = ''; + break; + } + case 'newline': + if (comment) + sep += source; + hasSpace = true; + break; + default: + onError(token, 'UNEXPECTED_TOKEN', `Unexpected ${type} at node end`); + } + offset += source.length; + } + } + return { comment, offset }; + } + + const blockMsg = 'Block collections are not allowed within flow collections'; + const isBlock = (token) => token && (token.type === 'block-map' || token.type === 'block-seq'); + function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onError, tag) { + const isMap = fc.start.source === '{'; + const fcName = isMap ? 'flow map' : 'flow sequence'; + const NodeClass = (tag?.nodeClass ?? (isMap ? YAMLMap : YAMLSeq)); + const coll = new NodeClass(ctx.schema); + coll.flow = true; + const atRoot = ctx.atRoot; + if (atRoot) + ctx.atRoot = false; + let offset = fc.offset + fc.start.source.length; + for (let i = 0; i < fc.items.length; ++i) { + const collItem = fc.items[i]; + const { start, key, sep, value } = collItem; + const props = resolveProps(start, { + flow: fcName, + indicator: 'explicit-key-ind', + next: key ?? sep?.[0], + offset, + onError, + parentIndent: fc.indent, + startOnNewline: false + }); + if (!props.found) { + if (!props.anchor && !props.tag && !sep && !value) { + if (i === 0 && props.comma) + onError(props.comma, 'UNEXPECTED_TOKEN', `Unexpected , in ${fcName}`); + else if (i < fc.items.length - 1) + onError(props.start, 'UNEXPECTED_TOKEN', `Unexpected empty item in ${fcName}`); + if (props.comment) { + if (coll.comment) + coll.comment += '\n' + props.comment; + else + coll.comment = props.comment; + } + offset = props.end; + continue; + } + if (!isMap && ctx.options.strict && containsNewline(key)) + onError(key, // checked by containsNewline() + 'MULTILINE_IMPLICIT_KEY', 'Implicit keys of flow sequence pairs need to be on a single line'); + } + if (i === 0) { + if (props.comma) + onError(props.comma, 'UNEXPECTED_TOKEN', `Unexpected , in ${fcName}`); + } + else { + if (!props.comma) + onError(props.start, 'MISSING_CHAR', `Missing , between ${fcName} items`); + if (props.comment) { + let prevItemComment = ''; + loop: for (const st of start) { + switch (st.type) { + case 'comma': + case 'space': + break; + case 'comment': + prevItemComment = st.source.substring(1); + break loop; + default: + break loop; + } + } + if (prevItemComment) { + let prev = coll.items[coll.items.length - 1]; + if (isPair(prev)) + prev = prev.value ?? prev.key; + if (prev.comment) + prev.comment += '\n' + prevItemComment; + else + prev.comment = prevItemComment; + props.comment = props.comment.substring(prevItemComment.length + 1); + } + } + } + if (!isMap && !sep && !props.found) { + // item is a value in a seq + // → key & sep are empty, start does not include ? or : + const valueNode = value + ? composeNode(ctx, value, props, onError) + : composeEmptyNode(ctx, props.end, sep, null, props, onError); + coll.items.push(valueNode); + offset = valueNode.range[2]; + if (isBlock(value)) + onError(valueNode.range, 'BLOCK_IN_FLOW', blockMsg); + } + else { + // item is a key+value pair + // key value + const keyStart = props.end; + const keyNode = key + ? composeNode(ctx, key, props, onError) + : composeEmptyNode(ctx, keyStart, start, null, props, onError); + if (isBlock(key)) + onError(keyNode.range, 'BLOCK_IN_FLOW', blockMsg); + // value properties + const valueProps = resolveProps(sep ?? [], { + flow: fcName, + indicator: 'map-value-ind', + next: value, + offset: keyNode.range[2], + onError, + parentIndent: fc.indent, + startOnNewline: false + }); + if (valueProps.found) { + if (!isMap && !props.found && ctx.options.strict) { + if (sep) + for (const st of sep) { + if (st === valueProps.found) + break; + if (st.type === 'newline') { + onError(st, 'MULTILINE_IMPLICIT_KEY', 'Implicit keys of flow sequence pairs need to be on a single line'); + break; + } + } + if (props.start < valueProps.found.offset - 1024) + onError(valueProps.found, 'KEY_OVER_1024_CHARS', 'The : indicator must be at most 1024 chars after the start of an implicit flow sequence key'); + } + } + else if (value) { + if ('source' in value && value.source && value.source[0] === ':') + onError(value, 'MISSING_CHAR', `Missing space after : in ${fcName}`); + else + onError(valueProps.start, 'MISSING_CHAR', `Missing , or : between ${fcName} items`); + } + // value value + const valueNode = value + ? composeNode(ctx, value, valueProps, onError) + : valueProps.found + ? composeEmptyNode(ctx, valueProps.end, sep, null, valueProps, onError) + : null; + if (valueNode) { + if (isBlock(value)) + onError(valueNode.range, 'BLOCK_IN_FLOW', blockMsg); + } + else if (valueProps.comment) { + if (keyNode.comment) + keyNode.comment += '\n' + valueProps.comment; + else + keyNode.comment = valueProps.comment; + } + const pair = new Pair(keyNode, valueNode); + if (ctx.options.keepSourceTokens) + pair.srcToken = collItem; + if (isMap) { + const map = coll; + if (mapIncludes(ctx, map.items, keyNode)) + onError(keyStart, 'DUPLICATE_KEY', 'Map keys must be unique'); + map.items.push(pair); + } + else { + const map = new YAMLMap(ctx.schema); + map.flow = true; + map.items.push(pair); + const endRange = (valueNode ?? keyNode).range; + map.range = [keyNode.range[0], endRange[1], endRange[2]]; + coll.items.push(map); + } + offset = valueNode ? valueNode.range[2] : valueProps.end; + } + } + const expectedEnd = isMap ? '}' : ']'; + const [ce, ...ee] = fc.end; + let cePos = offset; + if (ce && ce.source === expectedEnd) + cePos = ce.offset + ce.source.length; + else { + const name = fcName[0].toUpperCase() + fcName.substring(1); + const msg = atRoot + ? `${name} must end with a ${expectedEnd}` + : `${name} in block collection must be sufficiently indented and end with a ${expectedEnd}`; + onError(offset, atRoot ? 'MISSING_CHAR' : 'BAD_INDENT', msg); + if (ce && ce.source.length !== 1) + ee.unshift(ce); + } + if (ee.length > 0) { + const end = resolveEnd(ee, cePos, ctx.options.strict, onError); + if (end.comment) { + if (coll.comment) + coll.comment += '\n' + end.comment; + else + coll.comment = end.comment; + } + coll.range = [fc.offset, cePos, end.offset]; + } + else { + coll.range = [fc.offset, cePos, cePos]; + } + return coll; + } + + function resolveCollection(CN, ctx, token, onError, tagName, tag) { + const coll = token.type === 'block-map' + ? resolveBlockMap(CN, ctx, token, onError, tag) + : token.type === 'block-seq' + ? resolveBlockSeq(CN, ctx, token, onError, tag) + : resolveFlowCollection(CN, ctx, token, onError, tag); + const Coll = coll.constructor; + // If we got a tagName matching the class, or the tag name is '!', + // then use the tagName from the node class used to create it. + if (tagName === '!' || tagName === Coll.tagName) { + coll.tag = Coll.tagName; + return coll; + } + if (tagName) + coll.tag = tagName; + return coll; + } + function composeCollection(CN, ctx, token, props, onError) { + const tagToken = props.tag; + const tagName = !tagToken + ? null + : ctx.directives.tagName(tagToken.source, msg => onError(tagToken, 'TAG_RESOLVE_FAILED', msg)); + if (token.type === 'block-seq') { + const { anchor, newlineAfterProp: nl } = props; + const lastProp = anchor && tagToken + ? anchor.offset > tagToken.offset + ? anchor + : tagToken + : (anchor ?? tagToken); + if (lastProp && (!nl || nl.offset < lastProp.offset)) { + const message = 'Missing newline after block sequence props'; + onError(lastProp, 'MISSING_CHAR', message); + } + } + const expType = token.type === 'block-map' + ? 'map' + : token.type === 'block-seq' + ? 'seq' + : token.start.source === '{' + ? 'map' + : 'seq'; + // shortcut: check if it's a generic YAMLMap or YAMLSeq + // before jumping into the custom tag logic. + if (!tagToken || + !tagName || + tagName === '!' || + (tagName === YAMLMap.tagName && expType === 'map') || + (tagName === YAMLSeq.tagName && expType === 'seq')) { + return resolveCollection(CN, ctx, token, onError, tagName); + } + let tag = ctx.schema.tags.find(t => t.tag === tagName && t.collection === expType); + if (!tag) { + const kt = ctx.schema.knownTags[tagName]; + if (kt && kt.collection === expType) { + ctx.schema.tags.push(Object.assign({}, kt, { default: false })); + tag = kt; + } + else { + if (kt?.collection) { + onError(tagToken, 'BAD_COLLECTION_TYPE', `${kt.tag} used for ${expType} collection, but expects ${kt.collection}`, true); + } + else { + onError(tagToken, 'TAG_RESOLVE_FAILED', `Unresolved tag: ${tagName}`, true); + } + return resolveCollection(CN, ctx, token, onError, tagName); + } + } + const coll = resolveCollection(CN, ctx, token, onError, tagName, tag); + const res = tag.resolve?.(coll, msg => onError(tagToken, 'TAG_RESOLVE_FAILED', msg), ctx.options) ?? coll; + const node = isNode(res) + ? res + : new Scalar(res); + node.range = coll.range; + node.tag = tagName; + if (tag?.format) + node.format = tag.format; + return node; + } + + function resolveBlockScalar(ctx, scalar, onError) { + const start = scalar.offset; + const header = parseBlockScalarHeader(scalar, ctx.options.strict, onError); + if (!header) + return { value: '', type: null, comment: '', range: [start, start, start] }; + const type = header.mode === '>' ? Scalar.BLOCK_FOLDED : Scalar.BLOCK_LITERAL; + const lines = scalar.source ? splitLines(scalar.source) : []; + // determine the end of content & start of chomping + let chompStart = lines.length; + for (let i = lines.length - 1; i >= 0; --i) { + const content = lines[i][1]; + if (content === '' || content === '\r') + chompStart = i; + else + break; + } + // shortcut for empty contents + if (chompStart === 0) { + const value = header.chomp === '+' && lines.length > 0 + ? '\n'.repeat(Math.max(1, lines.length - 1)) + : ''; + let end = start + header.length; + if (scalar.source) + end += scalar.source.length; + return { value, type, comment: header.comment, range: [start, end, end] }; + } + // find the indentation level to trim from start + let trimIndent = scalar.indent + header.indent; + let offset = scalar.offset + header.length; + let contentStart = 0; + for (let i = 0; i < chompStart; ++i) { + const [indent, content] = lines[i]; + if (content === '' || content === '\r') { + if (header.indent === 0 && indent.length > trimIndent) + trimIndent = indent.length; + } + else { + if (indent.length < trimIndent) { + const message = 'Block scalars with more-indented leading empty lines must use an explicit indentation indicator'; + onError(offset + indent.length, 'MISSING_CHAR', message); + } + if (header.indent === 0) + trimIndent = indent.length; + contentStart = i; + if (trimIndent === 0 && !ctx.atRoot) { + const message = 'Block scalar values in collections must be indented'; + onError(offset, 'BAD_INDENT', message); + } + break; + } + offset += indent.length + content.length + 1; + } + // include trailing more-indented empty lines in content + for (let i = lines.length - 1; i >= chompStart; --i) { + if (lines[i][0].length > trimIndent) + chompStart = i + 1; + } + let value = ''; + let sep = ''; + let prevMoreIndented = false; + // leading whitespace is kept intact + for (let i = 0; i < contentStart; ++i) + value += lines[i][0].slice(trimIndent) + '\n'; + for (let i = contentStart; i < chompStart; ++i) { + let [indent, content] = lines[i]; + offset += indent.length + content.length + 1; + const crlf = content[content.length - 1] === '\r'; + if (crlf) + content = content.slice(0, -1); + /* istanbul ignore if already caught in lexer */ + if (content && indent.length < trimIndent) { + const src = header.indent + ? 'explicit indentation indicator' + : 'first line'; + const message = `Block scalar lines must not be less indented than their ${src}`; + onError(offset - content.length - (crlf ? 2 : 1), 'BAD_INDENT', message); + indent = ''; + } + if (type === Scalar.BLOCK_LITERAL) { + value += sep + indent.slice(trimIndent) + content; + sep = '\n'; + } + else if (indent.length > trimIndent || content[0] === '\t') { + // more-indented content within a folded block + if (sep === ' ') + sep = '\n'; + else if (!prevMoreIndented && sep === '\n') + sep = '\n\n'; + value += sep + indent.slice(trimIndent) + content; + sep = '\n'; + prevMoreIndented = true; + } + else if (content === '') { + // empty line + if (sep === '\n') + value += '\n'; + else + sep = '\n'; + } + else { + value += sep + content; + sep = ' '; + prevMoreIndented = false; + } + } + switch (header.chomp) { + case '-': + break; + case '+': + for (let i = chompStart; i < lines.length; ++i) + value += '\n' + lines[i][0].slice(trimIndent); + if (value[value.length - 1] !== '\n') + value += '\n'; + break; + default: + value += '\n'; + } + const end = start + header.length + scalar.source.length; + return { value, type, comment: header.comment, range: [start, end, end] }; + } + function parseBlockScalarHeader({ offset, props }, strict, onError) { + /* istanbul ignore if should not happen */ + if (props[0].type !== 'block-scalar-header') { + onError(props[0], 'IMPOSSIBLE', 'Block scalar header not found'); + return null; + } + const { source } = props[0]; + const mode = source[0]; + let indent = 0; + let chomp = ''; + let error = -1; + for (let i = 1; i < source.length; ++i) { + const ch = source[i]; + if (!chomp && (ch === '-' || ch === '+')) + chomp = ch; + else { + const n = Number(ch); + if (!indent && n) + indent = n; + else if (error === -1) + error = offset + i; + } + } + if (error !== -1) + onError(error, 'UNEXPECTED_TOKEN', `Block scalar header includes extra characters: ${source}`); + let hasSpace = false; + let comment = ''; + let length = source.length; + for (let i = 1; i < props.length; ++i) { + const token = props[i]; + switch (token.type) { + case 'space': + hasSpace = true; + // fallthrough + case 'newline': + length += token.source.length; + break; + case 'comment': + if (strict && !hasSpace) { + const message = 'Comments must be separated from other tokens by white space characters'; + onError(token, 'MISSING_CHAR', message); + } + length += token.source.length; + comment = token.source.substring(1); + break; + case 'error': + onError(token, 'UNEXPECTED_TOKEN', token.message); + length += token.source.length; + break; + /* istanbul ignore next should not happen */ + default: { + const message = `Unexpected token in block scalar header: ${token.type}`; + onError(token, 'UNEXPECTED_TOKEN', message); + const ts = token.source; + if (ts && typeof ts === 'string') + length += ts.length; + } + } + } + return { mode, indent, chomp, comment, length }; + } + /** @returns Array of lines split up as `[indent, content]` */ + function splitLines(source) { + const split = source.split(/\n( *)/); + const first = split[0]; + const m = first.match(/^( *)/); + const line0 = m?.[1] + ? [m[1], first.slice(m[1].length)] + : ['', first]; + const lines = [line0]; + for (let i = 1; i < split.length; i += 2) + lines.push([split[i], split[i + 1]]); + return lines; + } + + function resolveFlowScalar(scalar, strict, onError) { + const { offset, type, source, end } = scalar; + let _type; + let value; + const _onError = (rel, code, msg) => onError(offset + rel, code, msg); + switch (type) { + case 'scalar': + _type = Scalar.PLAIN; + value = plainValue(source, _onError); + break; + case 'single-quoted-scalar': + _type = Scalar.QUOTE_SINGLE; + value = singleQuotedValue(source, _onError); + break; + case 'double-quoted-scalar': + _type = Scalar.QUOTE_DOUBLE; + value = doubleQuotedValue(source, _onError); + break; + /* istanbul ignore next should not happen */ + default: + onError(scalar, 'UNEXPECTED_TOKEN', `Expected a flow scalar value, but found: ${type}`); + return { + value: '', + type: null, + comment: '', + range: [offset, offset + source.length, offset + source.length] + }; + } + const valueEnd = offset + source.length; + const re = resolveEnd(end, valueEnd, strict, onError); + return { + value, + type: _type, + comment: re.comment, + range: [offset, valueEnd, re.offset] + }; + } + function plainValue(source, onError) { + let badChar = ''; + switch (source[0]) { + /* istanbul ignore next should not happen */ + case '\t': + badChar = 'a tab character'; + break; + case ',': + badChar = 'flow indicator character ,'; + break; + case '%': + badChar = 'directive indicator character %'; + break; + case '|': + case '>': { + badChar = `block scalar indicator ${source[0]}`; + break; + } + case '@': + case '`': { + badChar = `reserved character ${source[0]}`; + break; + } + } + if (badChar) + onError(0, 'BAD_SCALAR_START', `Plain value cannot start with ${badChar}`); + return foldLines(source); + } + function singleQuotedValue(source, onError) { + if (source[source.length - 1] !== "'" || source.length === 1) + onError(source.length, 'MISSING_CHAR', "Missing closing 'quote"); + return foldLines(source.slice(1, -1)).replace(/''/g, "'"); + } + function foldLines(source) { + /** + * The negative lookbehind here and in the `re` RegExp is to + * prevent causing a polynomial search time in certain cases. + * + * The try-catch is for Safari, which doesn't support this yet: + * https://caniuse.com/js-regexp-lookbehind + */ + let first, line; + try { + first = new RegExp('(.*?)(? wsStart ? source.slice(wsStart, i + 1) : ch; + } + else { + res += ch; + } + } + if (source[source.length - 1] !== '"' || source.length === 1) + onError(source.length, 'MISSING_CHAR', 'Missing closing "quote'); + return res; + } + /** + * Fold a single newline into a space, multiple newlines to N - 1 newlines. + * Presumes `source[offset] === '\n'` + */ + function foldNewline(source, offset) { + let fold = ''; + let ch = source[offset + 1]; + while (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') { + if (ch === '\r' && source[offset + 2] !== '\n') + break; + if (ch === '\n') + fold += '\n'; + offset += 1; + ch = source[offset + 1]; + } + if (!fold) + fold = ' '; + return { fold, offset }; + } + const escapeCodes = { + '0': '\0', // null character + a: '\x07', // bell character + b: '\b', // backspace + e: '\x1b', // escape character + f: '\f', // form feed + n: '\n', // line feed + r: '\r', // carriage return + t: '\t', // horizontal tab + v: '\v', // vertical tab + N: '\u0085', // Unicode next line + _: '\u00a0', // Unicode non-breaking space + L: '\u2028', // Unicode line separator + P: '\u2029', // Unicode paragraph separator + ' ': ' ', + '"': '"', + '/': '/', + '\\': '\\', + '\t': '\t' + }; + function parseCharCode(source, offset, length, onError) { + const cc = source.substr(offset, length); + const ok = cc.length === length && /^[0-9a-fA-F]+$/.test(cc); + const code = ok ? parseInt(cc, 16) : NaN; + if (isNaN(code)) { + const raw = source.substr(offset - 2, length + 2); + onError(offset - 2, 'BAD_DQ_ESCAPE', `Invalid escape sequence ${raw}`); + return raw; + } + return String.fromCodePoint(code); + } + + function composeScalar(ctx, token, tagToken, onError) { + const { value, type, comment, range } = token.type === 'block-scalar' + ? resolveBlockScalar(ctx, token, onError) + : resolveFlowScalar(token, ctx.options.strict, onError); + const tagName = tagToken + ? ctx.directives.tagName(tagToken.source, msg => onError(tagToken, 'TAG_RESOLVE_FAILED', msg)) + : null; + const tag = tagToken && tagName + ? findScalarTagByName(ctx.schema, value, tagName, tagToken, onError) + : token.type === 'scalar' + ? findScalarTagByTest(ctx, value, token, onError) + : ctx.schema[SCALAR$1]; + let scalar; + try { + const res = tag.resolve(value, msg => onError(tagToken ?? token, 'TAG_RESOLVE_FAILED', msg), ctx.options); + scalar = isScalar$1(res) ? res : new Scalar(res); + } + catch (error) { + const msg = error instanceof Error ? error.message : String(error); + onError(tagToken ?? token, 'TAG_RESOLVE_FAILED', msg); + scalar = new Scalar(value); + } + scalar.range = range; + scalar.source = value; + if (type) + scalar.type = type; + if (tagName) + scalar.tag = tagName; + if (tag.format) + scalar.format = tag.format; + if (comment) + scalar.comment = comment; + return scalar; + } + function findScalarTagByName(schema, value, tagName, tagToken, onError) { + if (tagName === '!') + return schema[SCALAR$1]; // non-specific tag + const matchWithTest = []; + for (const tag of schema.tags) { + if (!tag.collection && tag.tag === tagName) { + if (tag.default && tag.test) + matchWithTest.push(tag); + else + return tag; + } + } + for (const tag of matchWithTest) + if (tag.test?.test(value)) + return tag; + const kt = schema.knownTags[tagName]; + if (kt && !kt.collection) { + // Ensure that the known tag is available for stringifying, + // but does not get used by default. + schema.tags.push(Object.assign({}, kt, { default: false, test: undefined })); + return kt; + } + onError(tagToken, 'TAG_RESOLVE_FAILED', `Unresolved tag: ${tagName}`, tagName !== 'tag:yaml.org,2002:str'); + return schema[SCALAR$1]; + } + function findScalarTagByTest({ directives, schema }, value, token, onError) { + const tag = schema.tags.find(tag => tag.default && tag.test?.test(value)) || schema[SCALAR$1]; + if (schema.compat) { + const compat = schema.compat.find(tag => tag.default && tag.test?.test(value)) ?? + schema[SCALAR$1]; + if (tag.tag !== compat.tag) { + const ts = directives.tagString(tag.tag); + const cs = directives.tagString(compat.tag); + const msg = `Value may be parsed as either ${ts} or ${cs}`; + onError(token, 'TAG_RESOLVE_FAILED', msg, true); + } + } + return tag; + } + + function emptyScalarPosition(offset, before, pos) { + if (before) { + if (pos === null) + pos = before.length; + for (let i = pos - 1; i >= 0; --i) { + let st = before[i]; + switch (st.type) { + case 'space': + case 'comment': + case 'newline': + offset -= st.source.length; + continue; + } + // Technically, an empty scalar is immediately after the last non-empty + // node, but it's more useful to place it after any whitespace. + st = before[++i]; + while (st?.type === 'space') { + offset += st.source.length; + st = before[++i]; + } + break; + } + } + return offset; + } + + const CN = { composeNode, composeEmptyNode }; + function composeNode(ctx, token, props, onError) { + const { spaceBefore, comment, anchor, tag } = props; + let node; + let isSrcToken = true; + switch (token.type) { + case 'alias': + node = composeAlias(ctx, token, onError); + if (anchor || tag) + onError(token, 'ALIAS_PROPS', 'An alias node must not specify any properties'); + break; + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': + case 'block-scalar': + node = composeScalar(ctx, token, tag, onError); + if (anchor) + node.anchor = anchor.source.substring(1); + break; + case 'block-map': + case 'block-seq': + case 'flow-collection': + node = composeCollection(CN, ctx, token, props, onError); + if (anchor) + node.anchor = anchor.source.substring(1); + break; + default: { + const message = token.type === 'error' + ? token.message + : `Unsupported token (type: ${token.type})`; + onError(token, 'UNEXPECTED_TOKEN', message); + node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError); + isSrcToken = false; + } + } + if (anchor && node.anchor === '') + onError(anchor, 'BAD_ALIAS', 'Anchor cannot be an empty string'); + if (spaceBefore) + node.spaceBefore = true; + if (comment) { + if (token.type === 'scalar' && token.source === '') + node.comment = comment; + else + node.commentBefore = comment; + } + // @ts-expect-error Type checking misses meaning of isSrcToken + if (ctx.options.keepSourceTokens && isSrcToken) + node.srcToken = token; + return node; + } + function composeEmptyNode(ctx, offset, before, pos, { spaceBefore, comment, anchor, tag, end }, onError) { + const token = { + type: 'scalar', + offset: emptyScalarPosition(offset, before, pos), + indent: -1, + source: '' + }; + const node = composeScalar(ctx, token, tag, onError); + if (anchor) { + node.anchor = anchor.source.substring(1); + if (node.anchor === '') + onError(anchor, 'BAD_ALIAS', 'Anchor cannot be an empty string'); + } + if (spaceBefore) + node.spaceBefore = true; + if (comment) { + node.comment = comment; + node.range[2] = end; + } + return node; + } + function composeAlias({ options }, { offset, source, end }, onError) { + const alias = new Alias(source.substring(1)); + if (alias.source === '') + onError(offset, 'BAD_ALIAS', 'Alias cannot be an empty string'); + if (alias.source.endsWith(':')) + onError(offset + source.length - 1, 'BAD_ALIAS', 'Alias ending in : is ambiguous', true); + const valueEnd = offset + source.length; + const re = resolveEnd(end, valueEnd, options.strict, onError); + alias.range = [offset, valueEnd, re.offset]; + if (re.comment) + alias.comment = re.comment; + return alias; + } + + function composeDoc(options, directives, { offset, start, value, end }, onError) { + const opts = Object.assign({ _directives: directives }, options); + const doc = new Document(undefined, opts); + const ctx = { + atRoot: true, + directives: doc.directives, + options: doc.options, + schema: doc.schema + }; + const props = resolveProps(start, { + indicator: 'doc-start', + next: value ?? end?.[0], + offset, + onError, + parentIndent: 0, + startOnNewline: true + }); + if (props.found) { + doc.directives.docStart = true; + if (value && + (value.type === 'block-map' || value.type === 'block-seq') && + !props.hasNewline) + onError(props.end, 'MISSING_CHAR', 'Block collection cannot start on same line with directives-end marker'); + } + // @ts-expect-error If Contents is set, let's trust the user + doc.contents = value + ? composeNode(ctx, value, props, onError) + : composeEmptyNode(ctx, props.end, start, null, props, onError); + const contentEnd = doc.contents.range[2]; + const re = resolveEnd(end, contentEnd, false, onError); + if (re.comment) + doc.comment = re.comment; + doc.range = [offset, contentEnd, re.offset]; + return doc; + } + + function getErrorPos(src) { + if (typeof src === 'number') + return [src, src + 1]; + if (Array.isArray(src)) + return src.length === 2 ? src : [src[0], src[1]]; + const { offset, source } = src; + return [offset, offset + (typeof source === 'string' ? source.length : 1)]; + } + function parsePrelude(prelude) { + let comment = ''; + let atComment = false; + let afterEmptyLine = false; + for (let i = 0; i < prelude.length; ++i) { + const source = prelude[i]; + switch (source[0]) { + case '#': + comment += + (comment === '' ? '' : afterEmptyLine ? '\n\n' : '\n') + + (source.substring(1) || ' '); + atComment = true; + afterEmptyLine = false; + break; + case '%': + if (prelude[i + 1]?.[0] !== '#') + i += 1; + atComment = false; + break; + default: + // This may be wrong after doc-end, but in that case it doesn't matter + if (!atComment) + afterEmptyLine = true; + atComment = false; + } + } + return { comment, afterEmptyLine }; + } + /** + * Compose a stream of CST nodes into a stream of YAML Documents. + * + * ```ts + * import { Composer, Parser } from 'yaml' + * + * const src: string = ... + * const tokens = new Parser().parse(src) + * const docs = new Composer().compose(tokens) + * ``` + */ + class Composer { + constructor(options = {}) { + this.doc = null; + this.atDirectives = false; + this.prelude = []; + this.errors = []; + this.warnings = []; + this.onError = (source, code, message, warning) => { + const pos = getErrorPos(source); + if (warning) + this.warnings.push(new YAMLWarning(pos, code, message)); + else + this.errors.push(new YAMLParseError(pos, code, message)); + }; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + this.directives = new Directives({ version: options.version || '1.2' }); + this.options = options; + } + decorate(doc, afterDoc) { + const { comment, afterEmptyLine } = parsePrelude(this.prelude); + //console.log({ dc: doc.comment, prelude, comment }) + if (comment) { + const dc = doc.contents; + if (afterDoc) { + doc.comment = doc.comment ? `${doc.comment}\n${comment}` : comment; + } + else if (afterEmptyLine || doc.directives.docStart || !dc) { + doc.commentBefore = comment; + } + else if (isCollection$1(dc) && !dc.flow && dc.items.length > 0) { + let it = dc.items[0]; + if (isPair(it)) + it = it.key; + const cb = it.commentBefore; + it.commentBefore = cb ? `${comment}\n${cb}` : comment; + } + else { + const cb = dc.commentBefore; + dc.commentBefore = cb ? `${comment}\n${cb}` : comment; + } + } + if (afterDoc) { + Array.prototype.push.apply(doc.errors, this.errors); + Array.prototype.push.apply(doc.warnings, this.warnings); + } + else { + doc.errors = this.errors; + doc.warnings = this.warnings; + } + this.prelude = []; + this.errors = []; + this.warnings = []; + } + /** + * Current stream status information. + * + * Mostly useful at the end of input for an empty stream. + */ + streamInfo() { + return { + comment: parsePrelude(this.prelude).comment, + directives: this.directives, + errors: this.errors, + warnings: this.warnings + }; + } + /** + * Compose tokens into documents. + * + * @param forceDoc - If the stream contains no document, still emit a final document including any comments and directives that would be applied to a subsequent document. + * @param endOffset - Should be set if `forceDoc` is also set, to set the document range end and to indicate errors correctly. + */ + *compose(tokens, forceDoc = false, endOffset = -1) { + for (const token of tokens) + yield* this.next(token); + yield* this.end(forceDoc, endOffset); + } + /** Advance the composer by one CST token. */ + *next(token) { + switch (token.type) { + case 'directive': + this.directives.add(token.source, (offset, message, warning) => { + const pos = getErrorPos(token); + pos[0] += offset; + this.onError(pos, 'BAD_DIRECTIVE', message, warning); + }); + this.prelude.push(token.source); + this.atDirectives = true; + break; + case 'document': { + const doc = composeDoc(this.options, this.directives, token, this.onError); + if (this.atDirectives && !doc.directives.docStart) + this.onError(token, 'MISSING_CHAR', 'Missing directives-end/doc-start indicator line'); + this.decorate(doc, false); + if (this.doc) + yield this.doc; + this.doc = doc; + this.atDirectives = false; + break; + } + case 'byte-order-mark': + case 'space': + break; + case 'comment': + case 'newline': + this.prelude.push(token.source); + break; + case 'error': { + const msg = token.source + ? `${token.message}: ${JSON.stringify(token.source)}` + : token.message; + const error = new YAMLParseError(getErrorPos(token), 'UNEXPECTED_TOKEN', msg); + if (this.atDirectives || !this.doc) + this.errors.push(error); + else + this.doc.errors.push(error); + break; + } + case 'doc-end': { + if (!this.doc) { + const msg = 'Unexpected doc-end without preceding document'; + this.errors.push(new YAMLParseError(getErrorPos(token), 'UNEXPECTED_TOKEN', msg)); + break; + } + this.doc.directives.docEnd = true; + const end = resolveEnd(token.end, token.offset + token.source.length, this.doc.options.strict, this.onError); + this.decorate(this.doc, true); + if (end.comment) { + const dc = this.doc.comment; + this.doc.comment = dc ? `${dc}\n${end.comment}` : end.comment; + } + this.doc.range[2] = end.offset; + break; + } + default: + this.errors.push(new YAMLParseError(getErrorPos(token), 'UNEXPECTED_TOKEN', `Unsupported token ${token.type}`)); + } + } + /** + * Call at end of input to yield any remaining document. + * + * @param forceDoc - If the stream contains no document, still emit a final document including any comments and directives that would be applied to a subsequent document. + * @param endOffset - Should be set if `forceDoc` is also set, to set the document range end and to indicate errors correctly. + */ + *end(forceDoc = false, endOffset = -1) { + if (this.doc) { + this.decorate(this.doc, true); + yield this.doc; + this.doc = null; + } + else if (forceDoc) { + const opts = Object.assign({ _directives: this.directives }, this.options); + const doc = new Document(undefined, opts); + if (this.atDirectives) + this.onError(endOffset, 'MISSING_CHAR', 'Missing directives-end indicator line'); + doc.range = [0, endOffset, endOffset]; + this.decorate(doc, false); + yield doc; + } + } + } + + function resolveAsScalar(token, strict = true, onError) { + if (token) { + const _onError = (pos, code, message) => { + const offset = typeof pos === 'number' ? pos : Array.isArray(pos) ? pos[0] : pos.offset; + if (onError) + onError(offset, code, message); + else + throw new YAMLParseError([offset, offset + 1], code, message); + }; + switch (token.type) { + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': + return resolveFlowScalar(token, strict, _onError); + case 'block-scalar': + return resolveBlockScalar({ options: { strict } }, token, _onError); + } + } + return null; + } + /** + * Create a new scalar token with `value` + * + * Values that represent an actual string but may be parsed as a different type should use a `type` other than `'PLAIN'`, + * as this function does not support any schema operations and won't check for such conflicts. + * + * @param value The string representation of the value, which will have its content properly indented. + * @param context.end Comments and whitespace after the end of the value, or after the block scalar header. If undefined, a newline will be added. + * @param context.implicitKey Being within an implicit key may affect the resolved type of the token's value. + * @param context.indent The indent level of the token. + * @param context.inFlow Is this scalar within a flow collection? This may affect the resolved type of the token's value. + * @param context.offset The offset position of the token. + * @param context.type The preferred type of the scalar token. If undefined, the previous type of the `token` will be used, defaulting to `'PLAIN'`. + */ + function createScalarToken(value, context) { + const { implicitKey = false, indent, inFlow = false, offset = -1, type = 'PLAIN' } = context; + const source = stringifyString({ type, value }, { + implicitKey, + indent: indent > 0 ? ' '.repeat(indent) : '', + inFlow, + options: { blockQuote: true, lineWidth: -1 } + }); + const end = context.end ?? [ + { type: 'newline', offset: -1, indent, source: '\n' } + ]; + switch (source[0]) { + case '|': + case '>': { + const he = source.indexOf('\n'); + const head = source.substring(0, he); + const body = source.substring(he + 1) + '\n'; + const props = [ + { type: 'block-scalar-header', offset, indent, source: head } + ]; + if (!addEndtoBlockProps(props, end)) + props.push({ type: 'newline', offset: -1, indent, source: '\n' }); + return { type: 'block-scalar', offset, indent, props, source: body }; + } + case '"': + return { type: 'double-quoted-scalar', offset, indent, source, end }; + case "'": + return { type: 'single-quoted-scalar', offset, indent, source, end }; + default: + return { type: 'scalar', offset, indent, source, end }; + } + } + /** + * Set the value of `token` to the given string `value`, overwriting any previous contents and type that it may have. + * + * Best efforts are made to retain any comments previously associated with the `token`, + * though all contents within a collection's `items` will be overwritten. + * + * Values that represent an actual string but may be parsed as a different type should use a `type` other than `'PLAIN'`, + * as this function does not support any schema operations and won't check for such conflicts. + * + * @param token Any token. If it does not include an `indent` value, the value will be stringified as if it were an implicit key. + * @param value The string representation of the value, which will have its content properly indented. + * @param context.afterKey In most cases, values after a key should have an additional level of indentation. + * @param context.implicitKey Being within an implicit key may affect the resolved type of the token's value. + * @param context.inFlow Being within a flow collection may affect the resolved type of the token's value. + * @param context.type The preferred type of the scalar token. If undefined, the previous type of the `token` will be used, defaulting to `'PLAIN'`. + */ + function setScalarValue(token, value, context = {}) { + let { afterKey = false, implicitKey = false, inFlow = false, type } = context; + let indent = 'indent' in token ? token.indent : null; + if (afterKey && typeof indent === 'number') + indent += 2; + if (!type) + switch (token.type) { + case 'single-quoted-scalar': + type = 'QUOTE_SINGLE'; + break; + case 'double-quoted-scalar': + type = 'QUOTE_DOUBLE'; + break; + case 'block-scalar': { + const header = token.props[0]; + if (header.type !== 'block-scalar-header') + throw new Error('Invalid block scalar header'); + type = header.source[0] === '>' ? 'BLOCK_FOLDED' : 'BLOCK_LITERAL'; + break; + } + default: + type = 'PLAIN'; + } + const source = stringifyString({ type, value }, { + implicitKey: implicitKey || indent === null, + indent: indent !== null && indent > 0 ? ' '.repeat(indent) : '', + inFlow, + options: { blockQuote: true, lineWidth: -1 } + }); + switch (source[0]) { + case '|': + case '>': + setBlockScalarValue(token, source); + break; + case '"': + setFlowScalarValue(token, source, 'double-quoted-scalar'); + break; + case "'": + setFlowScalarValue(token, source, 'single-quoted-scalar'); + break; + default: + setFlowScalarValue(token, source, 'scalar'); + } + } + function setBlockScalarValue(token, source) { + const he = source.indexOf('\n'); + const head = source.substring(0, he); + const body = source.substring(he + 1) + '\n'; + if (token.type === 'block-scalar') { + const header = token.props[0]; + if (header.type !== 'block-scalar-header') + throw new Error('Invalid block scalar header'); + header.source = head; + token.source = body; + } + else { + const { offset } = token; + const indent = 'indent' in token ? token.indent : -1; + const props = [ + { type: 'block-scalar-header', offset, indent, source: head } + ]; + if (!addEndtoBlockProps(props, 'end' in token ? token.end : undefined)) + props.push({ type: 'newline', offset: -1, indent, source: '\n' }); + for (const key of Object.keys(token)) + if (key !== 'type' && key !== 'offset') + delete token[key]; + Object.assign(token, { type: 'block-scalar', indent, props, source: body }); + } + } + /** @returns `true` if last token is a newline */ + function addEndtoBlockProps(props, end) { + if (end) + for (const st of end) + switch (st.type) { + case 'space': + case 'comment': + props.push(st); + break; + case 'newline': + props.push(st); + return true; + } + return false; + } + function setFlowScalarValue(token, source, type) { + switch (token.type) { + case 'scalar': + case 'double-quoted-scalar': + case 'single-quoted-scalar': + token.type = type; + token.source = source; + break; + case 'block-scalar': { + const end = token.props.slice(1); + let oa = source.length; + if (token.props[0].type === 'block-scalar-header') + oa -= token.props[0].source.length; + for (const tok of end) + tok.offset += oa; + delete token.props; + Object.assign(token, { type, source, end }); + break; + } + case 'block-map': + case 'block-seq': { + const offset = token.offset + source.length; + const nl = { type: 'newline', offset, indent: token.indent, source: '\n' }; + delete token.items; + Object.assign(token, { type, source, end: [nl] }); + break; + } + default: { + const indent = 'indent' in token ? token.indent : -1; + const end = 'end' in token && Array.isArray(token.end) + ? token.end.filter(st => st.type === 'space' || + st.type === 'comment' || + st.type === 'newline') + : []; + for (const key of Object.keys(token)) + if (key !== 'type' && key !== 'offset') + delete token[key]; + Object.assign(token, { type, indent, source, end }); + } + } + } + + /** + * Stringify a CST document, token, or collection item + * + * Fair warning: This applies no validation whatsoever, and + * simply concatenates the sources in their logical order. + */ + const stringify$1 = (cst) => 'type' in cst ? stringifyToken(cst) : stringifyItem(cst); + function stringifyToken(token) { + switch (token.type) { + case 'block-scalar': { + let res = ''; + for (const tok of token.props) + res += stringifyToken(tok); + return res + token.source; + } + case 'block-map': + case 'block-seq': { + let res = ''; + for (const item of token.items) + res += stringifyItem(item); + return res; + } + case 'flow-collection': { + let res = token.start.source; + for (const item of token.items) + res += stringifyItem(item); + for (const st of token.end) + res += st.source; + return res; + } + case 'document': { + let res = stringifyItem(token); + if (token.end) + for (const st of token.end) + res += st.source; + return res; + } + default: { + let res = token.source; + if ('end' in token && token.end) + for (const st of token.end) + res += st.source; + return res; + } + } + } + function stringifyItem({ start, key, sep, value }) { + let res = ''; + for (const st of start) + res += st.source; + if (key) + res += stringifyToken(key); + if (sep) + for (const st of sep) + res += st.source; + if (value) + res += stringifyToken(value); + return res; + } + + const BREAK = Symbol('break visit'); + const SKIP = Symbol('skip children'); + const REMOVE = Symbol('remove item'); + /** + * Apply a visitor to a CST document or item. + * + * Walks through the tree (depth-first) starting from the root, calling a + * `visitor` function with two arguments when entering each item: + * - `item`: The current item, which included the following members: + * - `start: SourceToken[]` – Source tokens before the key or value, + * possibly including its anchor or tag. + * - `key?: Token | null` – Set for pair values. May then be `null`, if + * the key before the `:` separator is empty. + * - `sep?: SourceToken[]` – Source tokens between the key and the value, + * which should include the `:` map value indicator if `value` is set. + * - `value?: Token` – The value of a sequence item, or of a map pair. + * - `path`: The steps from the root to the current node, as an array of + * `['key' | 'value', number]` tuples. + * + * The return value of the visitor may be used to control the traversal: + * - `undefined` (default): Do nothing and continue + * - `visit.SKIP`: Do not visit the children of this token, continue with + * next sibling + * - `visit.BREAK`: Terminate traversal completely + * - `visit.REMOVE`: Remove the current item, then continue with the next one + * - `number`: Set the index of the next step. This is useful especially if + * the index of the current token has changed. + * - `function`: Define the next visitor for this item. After the original + * visitor is called on item entry, next visitors are called after handling + * a non-empty `key` and when exiting the item. + */ + function visit(cst, visitor) { + if ('type' in cst && cst.type === 'document') + cst = { start: cst.start, value: cst.value }; + _visit(Object.freeze([]), cst, visitor); + } + // Without the `as symbol` casts, TS declares these in the `visit` + // namespace using `var`, but then complains about that because + // `unique symbol` must be `const`. + /** Terminate visit traversal completely */ + visit.BREAK = BREAK; + /** Do not visit the children of the current item */ + visit.SKIP = SKIP; + /** Remove the current item */ + visit.REMOVE = REMOVE; + /** Find the item at `path` from `cst` as the root */ + visit.itemAtPath = (cst, path) => { + let item = cst; + for (const [field, index] of path) { + const tok = item?.[field]; + if (tok && 'items' in tok) { + item = tok.items[index]; + } + else + return undefined; + } + return item; + }; + /** + * Get the immediate parent collection of the item at `path` from `cst` as the root. + * + * Throws an error if the collection is not found, which should never happen if the item itself exists. + */ + visit.parentCollection = (cst, path) => { + const parent = visit.itemAtPath(cst, path.slice(0, -1)); + const field = path[path.length - 1][0]; + const coll = parent?.[field]; + if (coll && 'items' in coll) + return coll; + throw new Error('Parent collection not found'); + }; + function _visit(path, item, visitor) { + let ctrl = visitor(item, path); + if (typeof ctrl === 'symbol') + return ctrl; + for (const field of ['key', 'value']) { + const token = item[field]; + if (token && 'items' in token) { + for (let i = 0; i < token.items.length; ++i) { + const ci = _visit(Object.freeze(path.concat([[field, i]])), token.items[i], visitor); + if (typeof ci === 'number') + i = ci - 1; + else if (ci === BREAK) + return BREAK; + else if (ci === REMOVE) { + token.items.splice(i, 1); + i -= 1; + } + } + if (typeof ctrl === 'function' && field === 'key') + ctrl = ctrl(item, path); + } + } + return typeof ctrl === 'function' ? ctrl(item, path) : ctrl; + } + + /** The byte order mark */ + const BOM = '\u{FEFF}'; + /** Start of doc-mode */ + const DOCUMENT = '\x02'; // C0: Start of Text + /** Unexpected end of flow-mode */ + const FLOW_END = '\x18'; // C0: Cancel + /** Next token is a scalar value */ + const SCALAR = '\x1f'; // C0: Unit Separator + /** @returns `true` if `token` is a flow or block collection */ + const isCollection = (token) => !!token && 'items' in token; + /** @returns `true` if `token` is a flow or block scalar; not an alias */ + const isScalar = (token) => !!token && + (token.type === 'scalar' || + token.type === 'single-quoted-scalar' || + token.type === 'double-quoted-scalar' || + token.type === 'block-scalar'); + /* istanbul ignore next */ + /** Get a printable representation of a lexer token */ + function prettyToken(token) { + switch (token) { + case BOM: + return ''; + case DOCUMENT: + return ''; + case FLOW_END: + return ''; + case SCALAR: + return ''; + default: + return JSON.stringify(token); + } + } + /** Identify the type of a lexer token. May return `null` for unknown tokens. */ + function tokenType(source) { + switch (source) { + case BOM: + return 'byte-order-mark'; + case DOCUMENT: + return 'doc-mode'; + case FLOW_END: + return 'flow-error-end'; + case SCALAR: + return 'scalar'; + case '---': + return 'doc-start'; + case '...': + return 'doc-end'; + case '': + case '\n': + case '\r\n': + return 'newline'; + case '-': + return 'seq-item-ind'; + case '?': + return 'explicit-key-ind'; + case ':': + return 'map-value-ind'; + case '{': + return 'flow-map-start'; + case '}': + return 'flow-map-end'; + case '[': + return 'flow-seq-start'; + case ']': + return 'flow-seq-end'; + case ',': + return 'comma'; + } + switch (source[0]) { + case ' ': + case '\t': + return 'space'; + case '#': + return 'comment'; + case '%': + return 'directive-line'; + case '*': + return 'alias'; + case '&': + return 'anchor'; + case '!': + return 'tag'; + case "'": + return 'single-quoted-scalar'; + case '"': + return 'double-quoted-scalar'; + case '|': + case '>': + return 'block-scalar-header'; + } + return null; + } + + var cst = /*#__PURE__*/Object.freeze({ + __proto__: null, + BOM: BOM, + DOCUMENT: DOCUMENT, + FLOW_END: FLOW_END, + SCALAR: SCALAR, + createScalarToken: createScalarToken, + isCollection: isCollection, + isScalar: isScalar, + prettyToken: prettyToken, + resolveAsScalar: resolveAsScalar, + setScalarValue: setScalarValue, + stringify: stringify$1, + tokenType: tokenType, + visit: visit + }); + + /* + START -> stream + + stream + directive -> line-end -> stream + indent + line-end -> stream + [else] -> line-start + + line-end + comment -> line-end + newline -> . + input-end -> END + + line-start + doc-start -> doc + doc-end -> stream + [else] -> indent -> block-start + + block-start + seq-item-start -> block-start + explicit-key-start -> block-start + map-value-start -> block-start + [else] -> doc + + doc + line-end -> line-start + spaces -> doc + anchor -> doc + tag -> doc + flow-start -> flow -> doc + flow-end -> error -> doc + seq-item-start -> error -> doc + explicit-key-start -> error -> doc + map-value-start -> doc + alias -> doc + quote-start -> quoted-scalar -> doc + block-scalar-header -> line-end -> block-scalar(min) -> line-start + [else] -> plain-scalar(false, min) -> doc + + flow + line-end -> flow + spaces -> flow + anchor -> flow + tag -> flow + flow-start -> flow -> flow + flow-end -> . + seq-item-start -> error -> flow + explicit-key-start -> flow + map-value-start -> flow + alias -> flow + quote-start -> quoted-scalar -> flow + comma -> flow + [else] -> plain-scalar(true, 0) -> flow + + quoted-scalar + quote-end -> . + [else] -> quoted-scalar + + block-scalar(min) + newline + peek(indent < min) -> . + [else] -> block-scalar(min) + + plain-scalar(is-flow, min) + scalar-end(is-flow) -> . + peek(newline + (indent < min)) -> . + [else] -> plain-scalar(min) + */ + function isEmpty(ch) { + switch (ch) { + case undefined: + case ' ': + case '\n': + case '\r': + case '\t': + return true; + default: + return false; + } + } + const hexDigits = new Set('0123456789ABCDEFabcdef'); + const tagChars = new Set("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-#;/?:@&=+$_.!~*'()"); + const flowIndicatorChars = new Set(',[]{}'); + const invalidAnchorChars = new Set(' ,[]{}\n\r\t'); + const isNotAnchorChar = (ch) => !ch || invalidAnchorChars.has(ch); + /** + * Splits an input string into lexical tokens, i.e. smaller strings that are + * easily identifiable by `tokens.tokenType()`. + * + * Lexing starts always in a "stream" context. Incomplete input may be buffered + * until a complete token can be emitted. + * + * In addition to slices of the original input, the following control characters + * may also be emitted: + * + * - `\x02` (Start of Text): A document starts with the next token + * - `\x18` (Cancel): Unexpected end of flow-mode (indicates an error) + * - `\x1f` (Unit Separator): Next token is a scalar value + * - `\u{FEFF}` (Byte order mark): Emitted separately outside documents + */ + class Lexer { + constructor() { + /** + * Flag indicating whether the end of the current buffer marks the end of + * all input + */ + this.atEnd = false; + /** + * Explicit indent set in block scalar header, as an offset from the current + * minimum indent, so e.g. set to 1 from a header `|2+`. Set to -1 if not + * explicitly set. + */ + this.blockScalarIndent = -1; + /** + * Block scalars that include a + (keep) chomping indicator in their header + * include trailing empty lines, which are otherwise excluded from the + * scalar's contents. + */ + this.blockScalarKeep = false; + /** Current input */ + this.buffer = ''; + /** + * Flag noting whether the map value indicator : can immediately follow this + * node within a flow context. + */ + this.flowKey = false; + /** Count of surrounding flow collection levels. */ + this.flowLevel = 0; + /** + * Minimum level of indentation required for next lines to be parsed as a + * part of the current scalar value. + */ + this.indentNext = 0; + /** Indentation level of the current line. */ + this.indentValue = 0; + /** Position of the next \n character. */ + this.lineEndPos = null; + /** Stores the state of the lexer if reaching the end of incpomplete input */ + this.next = null; + /** A pointer to `buffer`; the current position of the lexer. */ + this.pos = 0; + } + /** + * Generate YAML tokens from the `source` string. If `incomplete`, + * a part of the last line may be left as a buffer for the next call. + * + * @returns A generator of lexical tokens + */ + *lex(source, incomplete = false) { + if (source) { + if (typeof source !== 'string') + throw TypeError('source is not a string'); + this.buffer = this.buffer ? this.buffer + source : source; + this.lineEndPos = null; + } + this.atEnd = !incomplete; + let next = this.next ?? 'stream'; + while (next && (incomplete || this.hasChars(1))) + next = yield* this.parseNext(next); + } + atLineEnd() { + let i = this.pos; + let ch = this.buffer[i]; + while (ch === ' ' || ch === '\t') + ch = this.buffer[++i]; + if (!ch || ch === '#' || ch === '\n') + return true; + if (ch === '\r') + return this.buffer[i + 1] === '\n'; + return false; + } + charAt(n) { + return this.buffer[this.pos + n]; + } + continueScalar(offset) { + let ch = this.buffer[offset]; + if (this.indentNext > 0) { + let indent = 0; + while (ch === ' ') + ch = this.buffer[++indent + offset]; + if (ch === '\r') { + const next = this.buffer[indent + offset + 1]; + if (next === '\n' || (!next && !this.atEnd)) + return offset + indent + 1; + } + return ch === '\n' || indent >= this.indentNext || (!ch && !this.atEnd) + ? offset + indent + : -1; + } + if (ch === '-' || ch === '.') { + const dt = this.buffer.substr(offset, 3); + if ((dt === '---' || dt === '...') && isEmpty(this.buffer[offset + 3])) + return -1; + } + return offset; + } + getLine() { + let end = this.lineEndPos; + if (typeof end !== 'number' || (end !== -1 && end < this.pos)) { + end = this.buffer.indexOf('\n', this.pos); + this.lineEndPos = end; + } + if (end === -1) + return this.atEnd ? this.buffer.substring(this.pos) : null; + if (this.buffer[end - 1] === '\r') + end -= 1; + return this.buffer.substring(this.pos, end); + } + hasChars(n) { + return this.pos + n <= this.buffer.length; + } + setNext(state) { + this.buffer = this.buffer.substring(this.pos); + this.pos = 0; + this.lineEndPos = null; + this.next = state; + return null; + } + peek(n) { + return this.buffer.substr(this.pos, n); + } + *parseNext(next) { + switch (next) { + case 'stream': + return yield* this.parseStream(); + case 'line-start': + return yield* this.parseLineStart(); + case 'block-start': + return yield* this.parseBlockStart(); + case 'doc': + return yield* this.parseDocument(); + case 'flow': + return yield* this.parseFlowCollection(); + case 'quoted-scalar': + return yield* this.parseQuotedScalar(); + case 'block-scalar': + return yield* this.parseBlockScalar(); + case 'plain-scalar': + return yield* this.parsePlainScalar(); + } + } + *parseStream() { + let line = this.getLine(); + if (line === null) + return this.setNext('stream'); + if (line[0] === BOM) { + yield* this.pushCount(1); + line = line.substring(1); + } + if (line[0] === '%') { + let dirEnd = line.length; + let cs = line.indexOf('#'); + while (cs !== -1) { + const ch = line[cs - 1]; + if (ch === ' ' || ch === '\t') { + dirEnd = cs - 1; + break; + } + else { + cs = line.indexOf('#', cs + 1); + } + } + while (true) { + const ch = line[dirEnd - 1]; + if (ch === ' ' || ch === '\t') + dirEnd -= 1; + else + break; + } + const n = (yield* this.pushCount(dirEnd)) + (yield* this.pushSpaces(true)); + yield* this.pushCount(line.length - n); // possible comment + this.pushNewline(); + return 'stream'; + } + if (this.atLineEnd()) { + const sp = yield* this.pushSpaces(true); + yield* this.pushCount(line.length - sp); + yield* this.pushNewline(); + return 'stream'; + } + yield DOCUMENT; + return yield* this.parseLineStart(); + } + *parseLineStart() { + const ch = this.charAt(0); + if (!ch && !this.atEnd) + return this.setNext('line-start'); + if (ch === '-' || ch === '.') { + if (!this.atEnd && !this.hasChars(4)) + return this.setNext('line-start'); + const s = this.peek(3); + if ((s === '---' || s === '...') && isEmpty(this.charAt(3))) { + yield* this.pushCount(3); + this.indentValue = 0; + this.indentNext = 0; + return s === '---' ? 'doc' : 'stream'; + } + } + this.indentValue = yield* this.pushSpaces(false); + if (this.indentNext > this.indentValue && !isEmpty(this.charAt(1))) + this.indentNext = this.indentValue; + return yield* this.parseBlockStart(); + } + *parseBlockStart() { + const [ch0, ch1] = this.peek(2); + if (!ch1 && !this.atEnd) + return this.setNext('block-start'); + if ((ch0 === '-' || ch0 === '?' || ch0 === ':') && isEmpty(ch1)) { + const n = (yield* this.pushCount(1)) + (yield* this.pushSpaces(true)); + this.indentNext = this.indentValue + 1; + this.indentValue += n; + return yield* this.parseBlockStart(); + } + return 'doc'; + } + *parseDocument() { + yield* this.pushSpaces(true); + const line = this.getLine(); + if (line === null) + return this.setNext('doc'); + let n = yield* this.pushIndicators(); + switch (line[n]) { + case '#': + yield* this.pushCount(line.length - n); + // fallthrough + case undefined: + yield* this.pushNewline(); + return yield* this.parseLineStart(); + case '{': + case '[': + yield* this.pushCount(1); + this.flowKey = false; + this.flowLevel = 1; + return 'flow'; + case '}': + case ']': + // this is an error + yield* this.pushCount(1); + return 'doc'; + case '*': + yield* this.pushUntil(isNotAnchorChar); + return 'doc'; + case '"': + case "'": + return yield* this.parseQuotedScalar(); + case '|': + case '>': + n += yield* this.parseBlockScalarHeader(); + n += yield* this.pushSpaces(true); + yield* this.pushCount(line.length - n); + yield* this.pushNewline(); + return yield* this.parseBlockScalar(); + default: + return yield* this.parsePlainScalar(); + } + } + *parseFlowCollection() { + let nl, sp; + let indent = -1; + do { + nl = yield* this.pushNewline(); + if (nl > 0) { + sp = yield* this.pushSpaces(false); + this.indentValue = indent = sp; + } + else { + sp = 0; + } + sp += yield* this.pushSpaces(true); + } while (nl + sp > 0); + const line = this.getLine(); + if (line === null) + return this.setNext('flow'); + if ((indent !== -1 && indent < this.indentNext && line[0] !== '#') || + (indent === 0 && + (line.startsWith('---') || line.startsWith('...')) && + isEmpty(line[3]))) { + // Allowing for the terminal ] or } at the same (rather than greater) + // indent level as the initial [ or { is technically invalid, but + // failing here would be surprising to users. + const atFlowEndMarker = indent === this.indentNext - 1 && + this.flowLevel === 1 && + (line[0] === ']' || line[0] === '}'); + if (!atFlowEndMarker) { + // this is an error + this.flowLevel = 0; + yield FLOW_END; + return yield* this.parseLineStart(); + } + } + let n = 0; + while (line[n] === ',') { + n += yield* this.pushCount(1); + n += yield* this.pushSpaces(true); + this.flowKey = false; + } + n += yield* this.pushIndicators(); + switch (line[n]) { + case undefined: + return 'flow'; + case '#': + yield* this.pushCount(line.length - n); + return 'flow'; + case '{': + case '[': + yield* this.pushCount(1); + this.flowKey = false; + this.flowLevel += 1; + return 'flow'; + case '}': + case ']': + yield* this.pushCount(1); + this.flowKey = true; + this.flowLevel -= 1; + return this.flowLevel ? 'flow' : 'doc'; + case '*': + yield* this.pushUntil(isNotAnchorChar); + return 'flow'; + case '"': + case "'": + this.flowKey = true; + return yield* this.parseQuotedScalar(); + case ':': { + const next = this.charAt(1); + if (this.flowKey || isEmpty(next) || next === ',') { + this.flowKey = false; + yield* this.pushCount(1); + yield* this.pushSpaces(true); + return 'flow'; + } + } + // fallthrough + default: + this.flowKey = false; + return yield* this.parsePlainScalar(); + } + } + *parseQuotedScalar() { + const quote = this.charAt(0); + let end = this.buffer.indexOf(quote, this.pos + 1); + if (quote === "'") { + while (end !== -1 && this.buffer[end + 1] === "'") + end = this.buffer.indexOf("'", end + 2); + } + else { + // double-quote + while (end !== -1) { + let n = 0; + while (this.buffer[end - 1 - n] === '\\') + n += 1; + if (n % 2 === 0) + break; + end = this.buffer.indexOf('"', end + 1); + } + } + // Only looking for newlines within the quotes + const qb = this.buffer.substring(0, end); + let nl = qb.indexOf('\n', this.pos); + if (nl !== -1) { + while (nl !== -1) { + const cs = this.continueScalar(nl + 1); + if (cs === -1) + break; + nl = qb.indexOf('\n', cs); + } + if (nl !== -1) { + // this is an error caused by an unexpected unindent + end = nl - (qb[nl - 1] === '\r' ? 2 : 1); + } + } + if (end === -1) { + if (!this.atEnd) + return this.setNext('quoted-scalar'); + end = this.buffer.length; + } + yield* this.pushToIndex(end + 1, false); + return this.flowLevel ? 'flow' : 'doc'; + } + *parseBlockScalarHeader() { + this.blockScalarIndent = -1; + this.blockScalarKeep = false; + let i = this.pos; + while (true) { + const ch = this.buffer[++i]; + if (ch === '+') + this.blockScalarKeep = true; + else if (ch > '0' && ch <= '9') + this.blockScalarIndent = Number(ch) - 1; + else if (ch !== '-') + break; + } + return yield* this.pushUntil(ch => isEmpty(ch) || ch === '#'); + } + *parseBlockScalar() { + let nl = this.pos - 1; // may be -1 if this.pos === 0 + let indent = 0; + let ch; + loop: for (let i = this.pos; (ch = this.buffer[i]); ++i) { + switch (ch) { + case ' ': + indent += 1; + break; + case '\n': + nl = i; + indent = 0; + break; + case '\r': { + const next = this.buffer[i + 1]; + if (!next && !this.atEnd) + return this.setNext('block-scalar'); + if (next === '\n') + break; + } // fallthrough + default: + break loop; + } + } + if (!ch && !this.atEnd) + return this.setNext('block-scalar'); + if (indent >= this.indentNext) { + if (this.blockScalarIndent === -1) + this.indentNext = indent; + else { + this.indentNext = + this.blockScalarIndent + (this.indentNext === 0 ? 1 : this.indentNext); + } + do { + const cs = this.continueScalar(nl + 1); + if (cs === -1) + break; + nl = this.buffer.indexOf('\n', cs); + } while (nl !== -1); + if (nl === -1) { + if (!this.atEnd) + return this.setNext('block-scalar'); + nl = this.buffer.length; + } + } + // Trailing insufficiently indented tabs are invalid. + // To catch that during parsing, we include them in the block scalar value. + let i = nl + 1; + ch = this.buffer[i]; + while (ch === ' ') + ch = this.buffer[++i]; + if (ch === '\t') { + while (ch === '\t' || ch === ' ' || ch === '\r' || ch === '\n') + ch = this.buffer[++i]; + nl = i - 1; + } + else if (!this.blockScalarKeep) { + do { + let i = nl - 1; + let ch = this.buffer[i]; + if (ch === '\r') + ch = this.buffer[--i]; + const lastChar = i; // Drop the line if last char not more indented + while (ch === ' ') + ch = this.buffer[--i]; + if (ch === '\n' && i >= this.pos && i + 1 + indent > lastChar) + nl = i; + else + break; + } while (true); + } + yield SCALAR; + yield* this.pushToIndex(nl + 1, true); + return yield* this.parseLineStart(); + } + *parsePlainScalar() { + const inFlow = this.flowLevel > 0; + let end = this.pos - 1; + let i = this.pos - 1; + let ch; + while ((ch = this.buffer[++i])) { + if (ch === ':') { + const next = this.buffer[i + 1]; + if (isEmpty(next) || (inFlow && flowIndicatorChars.has(next))) + break; + end = i; + } + else if (isEmpty(ch)) { + let next = this.buffer[i + 1]; + if (ch === '\r') { + if (next === '\n') { + i += 1; + ch = '\n'; + next = this.buffer[i + 1]; + } + else + end = i; + } + if (next === '#' || (inFlow && flowIndicatorChars.has(next))) + break; + if (ch === '\n') { + const cs = this.continueScalar(i + 1); + if (cs === -1) + break; + i = Math.max(i, cs - 2); // to advance, but still account for ' #' + } + } + else { + if (inFlow && flowIndicatorChars.has(ch)) + break; + end = i; + } + } + if (!ch && !this.atEnd) + return this.setNext('plain-scalar'); + yield SCALAR; + yield* this.pushToIndex(end + 1, true); + return inFlow ? 'flow' : 'doc'; + } + *pushCount(n) { + if (n > 0) { + yield this.buffer.substr(this.pos, n); + this.pos += n; + return n; + } + return 0; + } + *pushToIndex(i, allowEmpty) { + const s = this.buffer.slice(this.pos, i); + if (s) { + yield s; + this.pos += s.length; + return s.length; + } + else if (allowEmpty) + yield ''; + return 0; + } + *pushIndicators() { + switch (this.charAt(0)) { + case '!': + return ((yield* this.pushTag()) + + (yield* this.pushSpaces(true)) + + (yield* this.pushIndicators())); + case '&': + return ((yield* this.pushUntil(isNotAnchorChar)) + + (yield* this.pushSpaces(true)) + + (yield* this.pushIndicators())); + case '-': // this is an error + case '?': // this is an error outside flow collections + case ':': { + const inFlow = this.flowLevel > 0; + const ch1 = this.charAt(1); + if (isEmpty(ch1) || (inFlow && flowIndicatorChars.has(ch1))) { + if (!inFlow) + this.indentNext = this.indentValue + 1; + else if (this.flowKey) + this.flowKey = false; + return ((yield* this.pushCount(1)) + + (yield* this.pushSpaces(true)) + + (yield* this.pushIndicators())); + } + } + } + return 0; + } + *pushTag() { + if (this.charAt(1) === '<') { + let i = this.pos + 2; + let ch = this.buffer[i]; + while (!isEmpty(ch) && ch !== '>') + ch = this.buffer[++i]; + return yield* this.pushToIndex(ch === '>' ? i + 1 : i, false); + } + else { + let i = this.pos + 1; + let ch = this.buffer[i]; + while (ch) { + if (tagChars.has(ch)) + ch = this.buffer[++i]; + else if (ch === '%' && + hexDigits.has(this.buffer[i + 1]) && + hexDigits.has(this.buffer[i + 2])) { + ch = this.buffer[(i += 3)]; + } + else + break; + } + return yield* this.pushToIndex(i, false); + } + } + *pushNewline() { + const ch = this.buffer[this.pos]; + if (ch === '\n') + return yield* this.pushCount(1); + else if (ch === '\r' && this.charAt(1) === '\n') + return yield* this.pushCount(2); + else + return 0; + } + *pushSpaces(allowTabs) { + let i = this.pos - 1; + let ch; + do { + ch = this.buffer[++i]; + } while (ch === ' ' || (allowTabs && ch === '\t')); + const n = i - this.pos; + if (n > 0) { + yield this.buffer.substr(this.pos, n); + this.pos = i; + } + return n; + } + *pushUntil(test) { + let i = this.pos; + let ch = this.buffer[i]; + while (!test(ch)) + ch = this.buffer[++i]; + return yield* this.pushToIndex(i, false); + } + } + + /** + * Tracks newlines during parsing in order to provide an efficient API for + * determining the one-indexed `{ line, col }` position for any offset + * within the input. + */ + class LineCounter { + constructor() { + this.lineStarts = []; + /** + * Should be called in ascending order. Otherwise, call + * `lineCounter.lineStarts.sort()` before calling `linePos()`. + */ + this.addNewLine = (offset) => this.lineStarts.push(offset); + /** + * Performs a binary search and returns the 1-indexed { line, col } + * position of `offset`. If `line === 0`, `addNewLine` has never been + * called or `offset` is before the first known newline. + */ + this.linePos = (offset) => { + let low = 0; + let high = this.lineStarts.length; + while (low < high) { + const mid = (low + high) >> 1; // Math.floor((low + high) / 2) + if (this.lineStarts[mid] < offset) + low = mid + 1; + else + high = mid; + } + if (this.lineStarts[low] === offset) + return { line: low + 1, col: 1 }; + if (low === 0) + return { line: 0, col: offset }; + const start = this.lineStarts[low - 1]; + return { line: low, col: offset - start + 1 }; + }; + } + } + + function includesToken(list, type) { + for (let i = 0; i < list.length; ++i) + if (list[i].type === type) + return true; + return false; + } + function findNonEmptyIndex(list) { + for (let i = 0; i < list.length; ++i) { + switch (list[i].type) { + case 'space': + case 'comment': + case 'newline': + break; + default: + return i; + } + } + return -1; + } + function isFlowToken(token) { + switch (token?.type) { + case 'alias': + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': + case 'flow-collection': + return true; + default: + return false; + } + } + function getPrevProps(parent) { + switch (parent.type) { + case 'document': + return parent.start; + case 'block-map': { + const it = parent.items[parent.items.length - 1]; + return it.sep ?? it.start; + } + case 'block-seq': + return parent.items[parent.items.length - 1].start; + /* istanbul ignore next should not happen */ + default: + return []; + } + } + /** Note: May modify input array */ + function getFirstKeyStartProps(prev) { + if (prev.length === 0) + return []; + let i = prev.length; + loop: while (--i >= 0) { + switch (prev[i].type) { + case 'doc-start': + case 'explicit-key-ind': + case 'map-value-ind': + case 'seq-item-ind': + case 'newline': + break loop; + } + } + while (prev[++i]?.type === 'space') { + /* loop */ + } + return prev.splice(i, prev.length); + } + function fixFlowSeqItems(fc) { + if (fc.start.type === 'flow-seq-start') { + for (const it of fc.items) { + if (it.sep && + !it.value && + !includesToken(it.start, 'explicit-key-ind') && + !includesToken(it.sep, 'map-value-ind')) { + if (it.key) + it.value = it.key; + delete it.key; + if (isFlowToken(it.value)) { + if (it.value.end) + Array.prototype.push.apply(it.value.end, it.sep); + else + it.value.end = it.sep; + } + else + Array.prototype.push.apply(it.start, it.sep); + delete it.sep; + } + } + } + } + /** + * A YAML concrete syntax tree (CST) parser + * + * ```ts + * const src: string = ... + * for (const token of new Parser().parse(src)) { + * // token: Token + * } + * ``` + * + * To use the parser with a user-provided lexer: + * + * ```ts + * function* parse(source: string, lexer: Lexer) { + * const parser = new Parser() + * for (const lexeme of lexer.lex(source)) + * yield* parser.next(lexeme) + * yield* parser.end() + * } + * + * const src: string = ... + * const lexer = new Lexer() + * for (const token of parse(src, lexer)) { + * // token: Token + * } + * ``` + */ + class Parser { + /** + * @param onNewLine - If defined, called separately with the start position of + * each new line (in `parse()`, including the start of input). + */ + constructor(onNewLine) { + /** If true, space and sequence indicators count as indentation */ + this.atNewLine = true; + /** If true, next token is a scalar value */ + this.atScalar = false; + /** Current indentation level */ + this.indent = 0; + /** Current offset since the start of parsing */ + this.offset = 0; + /** On the same line with a block map key */ + this.onKeyLine = false; + /** Top indicates the node that's currently being built */ + this.stack = []; + /** The source of the current token, set in parse() */ + this.source = ''; + /** The type of the current token, set in parse() */ + this.type = ''; + // Must be defined after `next()` + this.lexer = new Lexer(); + this.onNewLine = onNewLine; + } + /** + * Parse `source` as a YAML stream. + * If `incomplete`, a part of the last line may be left as a buffer for the next call. + * + * Errors are not thrown, but yielded as `{ type: 'error', message }` tokens. + * + * @returns A generator of tokens representing each directive, document, and other structure. + */ + *parse(source, incomplete = false) { + if (this.onNewLine && this.offset === 0) + this.onNewLine(0); + for (const lexeme of this.lexer.lex(source, incomplete)) + yield* this.next(lexeme); + if (!incomplete) + yield* this.end(); + } + /** + * Advance the parser by the `source` of one lexical token. + */ + *next(source) { + this.source = source; + if (this.atScalar) { + this.atScalar = false; + yield* this.step(); + this.offset += source.length; + return; + } + const type = tokenType(source); + if (!type) { + const message = `Not a YAML token: ${source}`; + yield* this.pop({ type: 'error', offset: this.offset, message, source }); + this.offset += source.length; + } + else if (type === 'scalar') { + this.atNewLine = false; + this.atScalar = true; + this.type = 'scalar'; + } + else { + this.type = type; + yield* this.step(); + switch (type) { + case 'newline': + this.atNewLine = true; + this.indent = 0; + if (this.onNewLine) + this.onNewLine(this.offset + source.length); + break; + case 'space': + if (this.atNewLine && source[0] === ' ') + this.indent += source.length; + break; + case 'explicit-key-ind': + case 'map-value-ind': + case 'seq-item-ind': + if (this.atNewLine) + this.indent += source.length; + break; + case 'doc-mode': + case 'flow-error-end': + return; + default: + this.atNewLine = false; + } + this.offset += source.length; + } + } + /** Call at end of input to push out any remaining constructions */ + *end() { + while (this.stack.length > 0) + yield* this.pop(); + } + get sourceToken() { + const st = { + type: this.type, + offset: this.offset, + indent: this.indent, + source: this.source + }; + return st; + } + *step() { + const top = this.peek(1); + if (this.type === 'doc-end' && (!top || top.type !== 'doc-end')) { + while (this.stack.length > 0) + yield* this.pop(); + this.stack.push({ + type: 'doc-end', + offset: this.offset, + source: this.source + }); + return; + } + if (!top) + return yield* this.stream(); + switch (top.type) { + case 'document': + return yield* this.document(top); + case 'alias': + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': + return yield* this.scalar(top); + case 'block-scalar': + return yield* this.blockScalar(top); + case 'block-map': + return yield* this.blockMap(top); + case 'block-seq': + return yield* this.blockSequence(top); + case 'flow-collection': + return yield* this.flowCollection(top); + case 'doc-end': + return yield* this.documentEnd(top); + } + /* istanbul ignore next should not happen */ + yield* this.pop(); + } + peek(n) { + return this.stack[this.stack.length - n]; + } + *pop(error) { + const token = error ?? this.stack.pop(); + /* istanbul ignore if should not happen */ + if (!token) { + const message = 'Tried to pop an empty stack'; + yield { type: 'error', offset: this.offset, source: '', message }; + } + else if (this.stack.length === 0) { + yield token; + } + else { + const top = this.peek(1); + if (token.type === 'block-scalar') { + // Block scalars use their parent rather than header indent + token.indent = 'indent' in top ? top.indent : 0; + } + else if (token.type === 'flow-collection' && top.type === 'document') { + // Ignore all indent for top-level flow collections + token.indent = 0; + } + if (token.type === 'flow-collection') + fixFlowSeqItems(token); + switch (top.type) { + case 'document': + top.value = token; + break; + case 'block-scalar': + top.props.push(token); // error + break; + case 'block-map': { + const it = top.items[top.items.length - 1]; + if (it.value) { + top.items.push({ start: [], key: token, sep: [] }); + this.onKeyLine = true; + return; + } + else if (it.sep) { + it.value = token; + } + else { + Object.assign(it, { key: token, sep: [] }); + this.onKeyLine = !it.explicitKey; + return; + } + break; + } + case 'block-seq': { + const it = top.items[top.items.length - 1]; + if (it.value) + top.items.push({ start: [], value: token }); + else + it.value = token; + break; + } + case 'flow-collection': { + const it = top.items[top.items.length - 1]; + if (!it || it.value) + top.items.push({ start: [], key: token, sep: [] }); + else if (it.sep) + it.value = token; + else + Object.assign(it, { key: token, sep: [] }); + return; + } + /* istanbul ignore next should not happen */ + default: + yield* this.pop(); + yield* this.pop(token); + } + if ((top.type === 'document' || + top.type === 'block-map' || + top.type === 'block-seq') && + (token.type === 'block-map' || token.type === 'block-seq')) { + const last = token.items[token.items.length - 1]; + if (last && + !last.sep && + !last.value && + last.start.length > 0 && + findNonEmptyIndex(last.start) === -1 && + (token.indent === 0 || + last.start.every(st => st.type !== 'comment' || st.indent < token.indent))) { + if (top.type === 'document') + top.end = last.start; + else + top.items.push({ start: last.start }); + token.items.splice(-1, 1); + } + } + } + } + *stream() { + switch (this.type) { + case 'directive-line': + yield { type: 'directive', offset: this.offset, source: this.source }; + return; + case 'byte-order-mark': + case 'space': + case 'comment': + case 'newline': + yield this.sourceToken; + return; + case 'doc-mode': + case 'doc-start': { + const doc = { + type: 'document', + offset: this.offset, + start: [] + }; + if (this.type === 'doc-start') + doc.start.push(this.sourceToken); + this.stack.push(doc); + return; + } + } + yield { + type: 'error', + offset: this.offset, + message: `Unexpected ${this.type} token in YAML stream`, + source: this.source + }; + } + *document(doc) { + if (doc.value) + return yield* this.lineEnd(doc); + switch (this.type) { + case 'doc-start': { + if (findNonEmptyIndex(doc.start) !== -1) { + yield* this.pop(); + yield* this.step(); + } + else + doc.start.push(this.sourceToken); + return; + } + case 'anchor': + case 'tag': + case 'space': + case 'comment': + case 'newline': + doc.start.push(this.sourceToken); + return; + } + const bv = this.startBlockValue(doc); + if (bv) + this.stack.push(bv); + else { + yield { + type: 'error', + offset: this.offset, + message: `Unexpected ${this.type} token in YAML document`, + source: this.source + }; + } + } + *scalar(scalar) { + if (this.type === 'map-value-ind') { + const prev = getPrevProps(this.peek(2)); + const start = getFirstKeyStartProps(prev); + let sep; + if (scalar.end) { + sep = scalar.end; + sep.push(this.sourceToken); + delete scalar.end; + } + else + sep = [this.sourceToken]; + const map = { + type: 'block-map', + offset: scalar.offset, + indent: scalar.indent, + items: [{ start, key: scalar, sep }] + }; + this.onKeyLine = true; + this.stack[this.stack.length - 1] = map; + } + else + yield* this.lineEnd(scalar); + } + *blockScalar(scalar) { + switch (this.type) { + case 'space': + case 'comment': + case 'newline': + scalar.props.push(this.sourceToken); + return; + case 'scalar': + scalar.source = this.source; + // block-scalar source includes trailing newline + this.atNewLine = true; + this.indent = 0; + if (this.onNewLine) { + let nl = this.source.indexOf('\n') + 1; + while (nl !== 0) { + this.onNewLine(this.offset + nl); + nl = this.source.indexOf('\n', nl) + 1; + } + } + yield* this.pop(); + break; + /* istanbul ignore next should not happen */ + default: + yield* this.pop(); + yield* this.step(); + } + } + *blockMap(map) { + const it = map.items[map.items.length - 1]; + // it.sep is true-ish if pair already has key or : separator + switch (this.type) { + case 'newline': + this.onKeyLine = false; + if (it.value) { + const end = 'end' in it.value ? it.value.end : undefined; + const last = Array.isArray(end) ? end[end.length - 1] : undefined; + if (last?.type === 'comment') + end?.push(this.sourceToken); + else + map.items.push({ start: [this.sourceToken] }); + } + else if (it.sep) { + it.sep.push(this.sourceToken); + } + else { + it.start.push(this.sourceToken); + } + return; + case 'space': + case 'comment': + if (it.value) { + map.items.push({ start: [this.sourceToken] }); + } + else if (it.sep) { + it.sep.push(this.sourceToken); + } + else { + if (this.atIndentedComment(it.start, map.indent)) { + const prev = map.items[map.items.length - 2]; + const end = prev?.value?.end; + if (Array.isArray(end)) { + Array.prototype.push.apply(end, it.start); + end.push(this.sourceToken); + map.items.pop(); + return; + } + } + it.start.push(this.sourceToken); + } + return; + } + if (this.indent >= map.indent) { + const atMapIndent = !this.onKeyLine && this.indent === map.indent; + const atNextItem = atMapIndent && + (it.sep || it.explicitKey) && + this.type !== 'seq-item-ind'; + // For empty nodes, assign newline-separated not indented empty tokens to following node + let start = []; + if (atNextItem && it.sep && !it.value) { + const nl = []; + for (let i = 0; i < it.sep.length; ++i) { + const st = it.sep[i]; + switch (st.type) { + case 'newline': + nl.push(i); + break; + case 'space': + break; + case 'comment': + if (st.indent > map.indent) + nl.length = 0; + break; + default: + nl.length = 0; + } + } + if (nl.length >= 2) + start = it.sep.splice(nl[1]); + } + switch (this.type) { + case 'anchor': + case 'tag': + if (atNextItem || it.value) { + start.push(this.sourceToken); + map.items.push({ start }); + this.onKeyLine = true; + } + else if (it.sep) { + it.sep.push(this.sourceToken); + } + else { + it.start.push(this.sourceToken); + } + return; + case 'explicit-key-ind': + if (!it.sep && !it.explicitKey) { + it.start.push(this.sourceToken); + it.explicitKey = true; + } + else if (atNextItem || it.value) { + start.push(this.sourceToken); + map.items.push({ start, explicitKey: true }); + } + else { + this.stack.push({ + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start: [this.sourceToken], explicitKey: true }] + }); + } + this.onKeyLine = true; + return; + case 'map-value-ind': + if (it.explicitKey) { + if (!it.sep) { + if (includesToken(it.start, 'newline')) { + Object.assign(it, { key: null, sep: [this.sourceToken] }); + } + else { + const start = getFirstKeyStartProps(it.start); + this.stack.push({ + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start, key: null, sep: [this.sourceToken] }] + }); + } + } + else if (it.value) { + map.items.push({ start: [], key: null, sep: [this.sourceToken] }); + } + else if (includesToken(it.sep, 'map-value-ind')) { + this.stack.push({ + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start, key: null, sep: [this.sourceToken] }] + }); + } + else if (isFlowToken(it.key) && + !includesToken(it.sep, 'newline')) { + const start = getFirstKeyStartProps(it.start); + const key = it.key; + const sep = it.sep; + sep.push(this.sourceToken); + // @ts-expect-error type guard is wrong here + delete it.key; + // @ts-expect-error type guard is wrong here + delete it.sep; + this.stack.push({ + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start, key, sep }] + }); + } + else if (start.length > 0) { + // Not actually at next item + it.sep = it.sep.concat(start, this.sourceToken); + } + else { + it.sep.push(this.sourceToken); + } + } + else { + if (!it.sep) { + Object.assign(it, { key: null, sep: [this.sourceToken] }); + } + else if (it.value || atNextItem) { + map.items.push({ start, key: null, sep: [this.sourceToken] }); + } + else if (includesToken(it.sep, 'map-value-ind')) { + this.stack.push({ + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start: [], key: null, sep: [this.sourceToken] }] + }); + } + else { + it.sep.push(this.sourceToken); + } + } + this.onKeyLine = true; + return; + case 'alias': + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': { + const fs = this.flowScalar(this.type); + if (atNextItem || it.value) { + map.items.push({ start, key: fs, sep: [] }); + this.onKeyLine = true; + } + else if (it.sep) { + this.stack.push(fs); + } + else { + Object.assign(it, { key: fs, sep: [] }); + this.onKeyLine = true; + } + return; + } + default: { + const bv = this.startBlockValue(map); + if (bv) { + if (atMapIndent && bv.type !== 'block-seq') { + map.items.push({ start }); + } + this.stack.push(bv); + return; + } + } + } + } + yield* this.pop(); + yield* this.step(); + } + *blockSequence(seq) { + const it = seq.items[seq.items.length - 1]; + switch (this.type) { + case 'newline': + if (it.value) { + const end = 'end' in it.value ? it.value.end : undefined; + const last = Array.isArray(end) ? end[end.length - 1] : undefined; + if (last?.type === 'comment') + end?.push(this.sourceToken); + else + seq.items.push({ start: [this.sourceToken] }); + } + else + it.start.push(this.sourceToken); + return; + case 'space': + case 'comment': + if (it.value) + seq.items.push({ start: [this.sourceToken] }); + else { + if (this.atIndentedComment(it.start, seq.indent)) { + const prev = seq.items[seq.items.length - 2]; + const end = prev?.value?.end; + if (Array.isArray(end)) { + Array.prototype.push.apply(end, it.start); + end.push(this.sourceToken); + seq.items.pop(); + return; + } + } + it.start.push(this.sourceToken); + } + return; + case 'anchor': + case 'tag': + if (it.value || this.indent <= seq.indent) + break; + it.start.push(this.sourceToken); + return; + case 'seq-item-ind': + if (this.indent !== seq.indent) + break; + if (it.value || includesToken(it.start, 'seq-item-ind')) + seq.items.push({ start: [this.sourceToken] }); + else + it.start.push(this.sourceToken); + return; + } + if (this.indent > seq.indent) { + const bv = this.startBlockValue(seq); + if (bv) { + this.stack.push(bv); + return; + } + } + yield* this.pop(); + yield* this.step(); + } + *flowCollection(fc) { + const it = fc.items[fc.items.length - 1]; + if (this.type === 'flow-error-end') { + let top; + do { + yield* this.pop(); + top = this.peek(1); + } while (top && top.type === 'flow-collection'); + } + else if (fc.end.length === 0) { + switch (this.type) { + case 'comma': + case 'explicit-key-ind': + if (!it || it.sep) + fc.items.push({ start: [this.sourceToken] }); + else + it.start.push(this.sourceToken); + return; + case 'map-value-ind': + if (!it || it.value) + fc.items.push({ start: [], key: null, sep: [this.sourceToken] }); + else if (it.sep) + it.sep.push(this.sourceToken); + else + Object.assign(it, { key: null, sep: [this.sourceToken] }); + return; + case 'space': + case 'comment': + case 'newline': + case 'anchor': + case 'tag': + if (!it || it.value) + fc.items.push({ start: [this.sourceToken] }); + else if (it.sep) + it.sep.push(this.sourceToken); + else + it.start.push(this.sourceToken); + return; + case 'alias': + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': { + const fs = this.flowScalar(this.type); + if (!it || it.value) + fc.items.push({ start: [], key: fs, sep: [] }); + else if (it.sep) + this.stack.push(fs); + else + Object.assign(it, { key: fs, sep: [] }); + return; + } + case 'flow-map-end': + case 'flow-seq-end': + fc.end.push(this.sourceToken); + return; + } + const bv = this.startBlockValue(fc); + /* istanbul ignore else should not happen */ + if (bv) + this.stack.push(bv); + else { + yield* this.pop(); + yield* this.step(); + } + } + else { + const parent = this.peek(2); + if (parent.type === 'block-map' && + ((this.type === 'map-value-ind' && parent.indent === fc.indent) || + (this.type === 'newline' && + !parent.items[parent.items.length - 1].sep))) { + yield* this.pop(); + yield* this.step(); + } + else if (this.type === 'map-value-ind' && + parent.type !== 'flow-collection') { + const prev = getPrevProps(parent); + const start = getFirstKeyStartProps(prev); + fixFlowSeqItems(fc); + const sep = fc.end.splice(1, fc.end.length); + sep.push(this.sourceToken); + const map = { + type: 'block-map', + offset: fc.offset, + indent: fc.indent, + items: [{ start, key: fc, sep }] + }; + this.onKeyLine = true; + this.stack[this.stack.length - 1] = map; + } + else { + yield* this.lineEnd(fc); + } + } + } + flowScalar(type) { + if (this.onNewLine) { + let nl = this.source.indexOf('\n') + 1; + while (nl !== 0) { + this.onNewLine(this.offset + nl); + nl = this.source.indexOf('\n', nl) + 1; + } + } + return { + type, + offset: this.offset, + indent: this.indent, + source: this.source + }; + } + startBlockValue(parent) { + switch (this.type) { + case 'alias': + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': + return this.flowScalar(this.type); + case 'block-scalar-header': + return { + type: 'block-scalar', + offset: this.offset, + indent: this.indent, + props: [this.sourceToken], + source: '' + }; + case 'flow-map-start': + case 'flow-seq-start': + return { + type: 'flow-collection', + offset: this.offset, + indent: this.indent, + start: this.sourceToken, + items: [], + end: [] + }; + case 'seq-item-ind': + return { + type: 'block-seq', + offset: this.offset, + indent: this.indent, + items: [{ start: [this.sourceToken] }] + }; + case 'explicit-key-ind': { + this.onKeyLine = true; + const prev = getPrevProps(parent); + const start = getFirstKeyStartProps(prev); + start.push(this.sourceToken); + return { + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start, explicitKey: true }] + }; + } + case 'map-value-ind': { + this.onKeyLine = true; + const prev = getPrevProps(parent); + const start = getFirstKeyStartProps(prev); + return { + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start, key: null, sep: [this.sourceToken] }] + }; + } + } + return null; + } + atIndentedComment(start, indent) { + if (this.type !== 'comment') + return false; + if (this.indent <= indent) + return false; + return start.every(st => st.type === 'newline' || st.type === 'space'); + } + *documentEnd(docEnd) { + if (this.type !== 'doc-mode') { + if (docEnd.end) + docEnd.end.push(this.sourceToken); + else + docEnd.end = [this.sourceToken]; + if (this.type === 'newline') + yield* this.pop(); + } + } + *lineEnd(token) { + switch (this.type) { + case 'comma': + case 'doc-start': + case 'doc-end': + case 'flow-seq-end': + case 'flow-map-end': + case 'map-value-ind': + yield* this.pop(); + yield* this.step(); + break; + case 'newline': + this.onKeyLine = false; + // fallthrough + case 'space': + case 'comment': + default: + // all other values are errors + if (token.end) + token.end.push(this.sourceToken); + else + token.end = [this.sourceToken]; + if (this.type === 'newline') + yield* this.pop(); + } + } + } + + function parseOptions(options) { + const prettyErrors = options.prettyErrors !== false; + const lineCounter = options.lineCounter || (prettyErrors && new LineCounter()) || null; + return { lineCounter, prettyErrors }; + } + /** + * Parse the input as a stream of YAML documents. + * + * Documents should be separated from each other by `...` or `---` marker lines. + * + * @returns If an empty `docs` array is returned, it will be of type + * EmptyStream and contain additional stream information. In + * TypeScript, you should use `'empty' in docs` as a type guard for it. + */ + function parseAllDocuments(source, options = {}) { + const { lineCounter, prettyErrors } = parseOptions(options); + const parser = new Parser(lineCounter?.addNewLine); + const composer = new Composer(options); + const docs = Array.from(composer.compose(parser.parse(source))); + if (prettyErrors && lineCounter) + for (const doc of docs) { + doc.errors.forEach(prettifyError(source, lineCounter)); + doc.warnings.forEach(prettifyError(source, lineCounter)); + } + if (docs.length > 0) + return docs; + return Object.assign([], { empty: true }, composer.streamInfo()); + } + /** Parse an input string into a single YAML.Document */ + function parseDocument(source, options = {}) { + const { lineCounter, prettyErrors } = parseOptions(options); + const parser = new Parser(lineCounter?.addNewLine); + const composer = new Composer(options); + // `doc` is always set by compose.end(true) at the very latest + let doc = null; + for (const _doc of composer.compose(parser.parse(source), true, source.length)) { + if (!doc) + doc = _doc; + else if (doc.options.logLevel !== 'silent') { + doc.errors.push(new YAMLParseError(_doc.range.slice(0, 2), 'MULTIPLE_DOCS', 'Source contains multiple documents; please use YAML.parseAllDocuments()')); + break; + } + } + if (prettyErrors && lineCounter) { + doc.errors.forEach(prettifyError(source, lineCounter)); + doc.warnings.forEach(prettifyError(source, lineCounter)); + } + return doc; + } + function parse(src, reviver, options) { + let _reviver = undefined; + if (typeof reviver === 'function') { + _reviver = reviver; + } + else if (options === undefined && reviver && typeof reviver === 'object') { + options = reviver; + } + const doc = parseDocument(src, options); + if (!doc) + return null; + doc.warnings.forEach(warning => warn(doc.options.logLevel, warning)); + if (doc.errors.length > 0) { + if (doc.options.logLevel !== 'silent') + throw doc.errors[0]; + else + doc.errors = []; + } + return doc.toJS(Object.assign({ reviver: _reviver }, options)); + } + function stringify(value, replacer, options) { + let _replacer = null; + if (typeof replacer === 'function' || Array.isArray(replacer)) { + _replacer = replacer; + } + else if (options === undefined && replacer) { + options = replacer; + } + if (typeof options === 'string') + options = options.length; + if (typeof options === 'number') { + const indent = Math.round(options); + options = indent < 1 ? undefined : indent > 8 ? { indent: 8 } : { indent }; + } + if (value === undefined) { + const { keepUndefined } = options ?? replacer ?? {}; + if (!keepUndefined) + return undefined; + } + return new Document(value, _replacer, options).toString(options); + } + + var YAML = /*#__PURE__*/Object.freeze({ + __proto__: null, + Alias: Alias, + CST: cst, + Composer: Composer, + Document: Document, + Lexer: Lexer, + LineCounter: LineCounter, + Pair: Pair, + Parser: Parser, + Scalar: Scalar, + Schema: Schema, + YAMLError: YAMLError, + YAMLMap: YAMLMap, + YAMLParseError: YAMLParseError, + YAMLSeq: YAMLSeq, + YAMLWarning: YAMLWarning, + isAlias: isAlias, + isCollection: isCollection$1, + isDocument: isDocument, + isMap: isMap, + isNode: isNode, + isPair: isPair, + isScalar: isScalar$1, + isSeq: isSeq, + parse: parse, + parseAllDocuments: parseAllDocuments, + parseDocument: parseDocument, + stringify: stringify, + visit: visit$1, + visitAsync: visitAsync + }); + + // `export * as default from ...` fails on Webpack v4 + // https://github.com/eemeli/yaml/issues/228 + + var index = /*#__PURE__*/Object.freeze({ + __proto__: null, + Alias: Alias, + CST: cst, + Composer: Composer, + Document: Document, + Lexer: Lexer, + LineCounter: LineCounter, + Pair: Pair, + Parser: Parser, + Scalar: Scalar, + Schema: Schema, + YAMLError: YAMLError, + YAMLMap: YAMLMap, + YAMLParseError: YAMLParseError, + YAMLSeq: YAMLSeq, + YAMLWarning: YAMLWarning, + default: YAML, + isAlias: isAlias, + isCollection: isCollection$1, + isDocument: isDocument, + isMap: isMap, + isNode: isNode, + isPair: isPair, + isScalar: isScalar$1, + isSeq: isSeq, + parse: parse, + parseAllDocuments: parseAllDocuments, + parseDocument: parseDocument, + stringify: stringify, + visit: visit$1, + visitAsync: visitAsync + }); + + exports.AppEmbed = AppEmbed; + exports.ConversationEmbed = ConversationEmbed; + exports.ConversationMessage = ConversationMessage; + exports.LiveboardEmbed = LiveboardEmbed; + exports.PinboardEmbed = PinboardEmbed; + exports.PreRenderedAppEmbed = PreRenderedAppEmbed; + exports.PreRenderedConversationEmbed = PreRenderedConversationEmbed; + exports.PreRenderedLiveboardEmbed = PreRenderedLiveboardEmbed; + exports.PreRenderedPinboardEmbed = PreRenderedPinboardEmbed; + exports.PreRenderedSearchBarEmbed = PreRenderedSearchBarEmbed; + exports.PreRenderedSearchEmbed = PreRenderedSearchEmbed; + exports.SearchBarEmbed = SearchBarEmbed; + exports.SearchEmbed = SearchEmbed; + exports.SpotterEmbed = SpotterEmbed; + exports.SpotterMessage = SpotterMessage; + exports.getSessionInfo = getSessionInfo; + exports.useEmbedRef = useEmbedRef; + exports.useInit = useInit; + exports.useSpotterAgent = useSpotterAgent; + +})); diff --git a/dist/tsembed.es.js b/dist/tsembed.es.js new file mode 100644 index 00000000..eb25d823 --- /dev/null +++ b/dist/tsembed.es.js @@ -0,0 +1,24487 @@ +/* @thoughtspot/visual-embed-sdk version 1.48.0 */ +'use client'; +function _mergeNamespaces(n, m) { + m.forEach(function (e) { + e && typeof e !== 'string' && !Array.isArray(e) && Object.keys(e).forEach(function (k) { + if (k !== 'default' && !(k in n)) { + var d = Object.getOwnPropertyDescriptor(e, k); + Object.defineProperty(n, k, d.get ? d : { + enumerable: true, + get: function () { return e[k]; } + }); + } + }); + }); + return Object.freeze(n); +} + +/** + * Copyright (c) 2023 + * + * TypeScript type definitions for ThoughtSpot Visual Embed SDK + * @summary Type definitions for Embed SDK + * @author Ayon Ghosh + */ +/** + * The authentication mechanism for allowing access to + * the embedded app + * @group Authentication / Init + */ +var AuthType; +(function (AuthType) { + /** + * No authentication on the SDK. Pass-through to the embedded App. Alias for + * `Passthrough`. + * @example + * ```js + * init({ + * // ... + * authType: AuthType.None, + * }); + * ``` + */ + AuthType["None"] = "None"; + /** + * Passthrough SSO to the embedded application within the iframe. Requires least + * configuration, but may not be supported by all IDPs. This will behave like `None` + * if SSO is not configured on ThoughtSpot. + * + * To use this: + * Your SAML or OpenID provider must allow iframe redirects. + * For example, if you are using Okta as IdP, you can enable iframe embedding. + * @version SDK: 1.15.0 | ThoughtSpot: 8.8.0.cl + * @example + * ```js + * init({ + * // ... + * authType: AuthType.EmbeddedSSO, + * }); + * ``` + */ + AuthType["EmbeddedSSO"] = "EmbeddedSSO"; + /** + * SSO using SAML, Use {@link SAMLRedirect} instead + * @deprecated This option is deprecated. + * @hidden + */ + AuthType["SSO"] = "SSO_SAML"; + /** + * SSO using SAML, Use {@link SAMLRedirect} instead + * @deprecated This option is deprecated. + * @hidden + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + AuthType["SAML"] = "SSO_SAML"; + /** + * SSO using SAML + * Makes the host application redirect to the SAML IdP. Use this + * if your IdP does not allow itself to be embedded. + * + * This redirects the host application to the SAML IdP. The host application + * will be redirected back to the ThoughtSpot app after authentication. + * @example + * ```js + * init({ + * // ... + * authType: AuthType.SAMLRedirect, + * }); + * ``` + * + * This opens the SAML IdP in a popup window. The popup is triggered + * when the user clicks the trigger button. The popup window will be + * closed automatically after authentication. + * @example + * ```js + * init({ + * // ... + * authType: AuthType.SAMLRedirect, + * authTriggerText: 'Login with SAML', + * authTriggerContainer: '#tsEmbed', + * inPopup: true, + * }); + * ``` + * + * Can also use the event to trigger the popup flow. Works the same + * as the above example. + * @example + * ```js + * const authEE = init({ + * // ... + * authType: AuthType.SAMLRedirect, + * inPopup: true, + * }); + * + * someButtonOnYourPage.addEventListener('click', () => { + * authEE.emit(AuthEvent.TRIGGER_SSO_POPUP); + * }); + * ``` + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + AuthType["SAMLRedirect"] = "SSO_SAML"; + /** + * SSO using OIDC + * SSO using OIDC, Use {@link OIDCRedirect} instead + * @deprecated This option is deprecated. + * @hidden + */ + AuthType["OIDC"] = "SSO_OIDC"; + /** + * SSO using OIDC + * Will make the host application redirect to the OIDC IdP. + * See code samples in {@link SAMLRedirect}. + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + AuthType["OIDCRedirect"] = "SSO_OIDC"; + /** + * Trusted authentication server + * Use {@link TrustedAuth} instead + * @deprecated This option is deprecated. + * @hidden + */ + AuthType["AuthServer"] = "AuthServer"; + /** + * Trusted authentication server. Use your own authentication server + * which returns a bearer token, generated using the `secret_key` obtained + * from ThoughtSpot. + * @example + * ```js + * init({ + * // ... + * authType: AuthType.TrustedAuthToken, + * getAuthToken: () => { + * return fetch('https://my-backend.app/ts-token') + * .then((response) => response.json()) + * .then((data) => data.token); + * } + * }); + * ``` + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + AuthType["TrustedAuthToken"] = "AuthServer"; + /** + * Trusted authentication server Cookieless, Use your own authentication + * server which returns a bearer token, generated using the `secret_key` + * obtained from ThoughtSpot. This uses a cookieless authentication + * approach, recommended to bypass the third-party cookie-blocking restriction + * implemented by some browsers. + * @version SDK: 1.22.0 | ThoughtSpot: 9.3.0.cl, 9.5.1.sw + * @example + * ```js + * init({ + * // ... + * authType: AuthType.TrustedAuthTokenCookieless, + * getAuthToken: () => { + * return fetch('https://my-backend.app/ts-token') + * .then((response) => response.json()) + * .then((data) => data.token); + * } + * }); + * ``` + */ + AuthType["TrustedAuthTokenCookieless"] = "AuthServerCookieless"; + /** + * Use the ThoughtSpot login API to authenticate to the cluster directly. + * + * Warning: This feature is primarily intended for developer testing. It is + * strongly advised not to use this authentication method in production. + */ + AuthType["Basic"] = "Basic"; +})(AuthType || (AuthType = {})); +/** + * + * **Note**: This attribute is not supported in the classic (V1) homepage experience. + * + */ +var HomeLeftNavItem; +(function (HomeLeftNavItem) { + /** + * The *Search data* option in + * the *Insights* left navigation panel. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + */ + HomeLeftNavItem["SearchData"] = "search-data"; + /** + * The *Home* menu option in + * the *Insights* left navigation panel. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + */ + HomeLeftNavItem["Home"] = "insights-home"; + /** + * The *Liveboards* menu option in + * the *Insights* left navigation panel. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + */ + HomeLeftNavItem["Liveboards"] = "liveboards"; + /** + * The *Answers* menu option in + * the *Insights* left navigation panel. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + */ + HomeLeftNavItem["Answers"] = "answers"; + /** + * The *Monitor subscriptions* menu option in + * the *Insights* left navigation panel. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + */ + HomeLeftNavItem["MonitorSubscription"] = "monitor-alerts"; + /** + * The *SpotIQ analysis* menu option in + * the *Insights* left navigation panel. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl + */ + HomeLeftNavItem["SpotIQAnalysis"] = "spotiq-analysis"; + /** + * The *Liveboard schedules* menu option in + * the *Insights* left navigation panel. + * @version SDK: 1.34.0 | ThoughtSpot: 10.3.0.cl + */ + HomeLeftNavItem["LiveboardSchedules"] = "liveboard-schedules"; + /** + * The create option in the *Insights* + * left navigation panel. + * Available in the V3 navigation experience. + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HomeLeftNavItem["Create"] = "create"; + /** + * The *Spotter* menu option in the *Insights* + * left navigation panel. + * Available in the V3 navigation experience. + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HomeLeftNavItem["Spotter"] = "spotter"; + /** + * The *Favorites* section in the *Insights* + * left navigation panel. + * Available in the V3 navigation experience. + * @version SDK: 1.41.0 | ThoughtSpot: 10.12.0.cl + */ + HomeLeftNavItem["Favorites"] = "favorites"; +})(HomeLeftNavItem || (HomeLeftNavItem = {})); +/** + * A map of the supported runtime filter operations + */ +var RuntimeFilterOp; +(function (RuntimeFilterOp) { + /** + * Equals + */ + RuntimeFilterOp["EQ"] = "EQ"; + /** + * Does not equal + */ + RuntimeFilterOp["NE"] = "NE"; + /** + * Less than + */ + RuntimeFilterOp["LT"] = "LT"; + /** + * Less than or equal to + */ + RuntimeFilterOp["LE"] = "LE"; + /** + * Greater than + */ + RuntimeFilterOp["GT"] = "GT"; + /** + * Greater than or equal to + */ + RuntimeFilterOp["GE"] = "GE"; + /** + * Contains + */ + RuntimeFilterOp["CONTAINS"] = "CONTAINS"; + /** + * Begins with + */ + RuntimeFilterOp["BEGINS_WITH"] = "BEGINS_WITH"; + /** + * Ends with + */ + RuntimeFilterOp["ENDS_WITH"] = "ENDS_WITH"; + /** + * Between, inclusive of higher value + */ + RuntimeFilterOp["BW_INC_MAX"] = "BW_INC_MAX"; + /** + * Between, inclusive of lower value + */ + RuntimeFilterOp["BW_INC_MIN"] = "BW_INC_MIN"; + /** + * Between, inclusive of both higher and lower value + */ + RuntimeFilterOp["BW_INC"] = "BW_INC"; + /** + * Between, non-inclusive + */ + RuntimeFilterOp["BW"] = "BW"; + /** + * Is included in this list of values + */ + RuntimeFilterOp["IN"] = "IN"; + /** + * Is not included in this list of values + */ + RuntimeFilterOp["NOT_IN"] = "NOT_IN"; +})(RuntimeFilterOp || (RuntimeFilterOp = {})); +/** + * Home page modules that can be hidden + * via `hiddenHomepageModules` and reordered via + * `reorderedHomepageModules`. + * + * **Note**: This option is not supported in the classic (v1) experience. + * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl, 10.1.0.sw + */ +var HomepageModule; +(function (HomepageModule) { + /** + * Search bar + */ + HomepageModule["Search"] = "SEARCH"; + /** + * KPI watchlist module + */ + HomepageModule["Watchlist"] = "WATCHLIST"; + /** + * Favorite module + */ + HomepageModule["Favorite"] = "FAVORITE"; + /** + * List of answers and Liveboards + */ + HomepageModule["MyLibrary"] = "MY_LIBRARY"; + /** + * Trending list + */ + HomepageModule["Trending"] = "TRENDING"; + /** + * Learning videos + */ + HomepageModule["Learning"] = "LEARNING"; +})(HomepageModule || (HomepageModule = {})); +/** + * List page columns that can be hidden. + * **Note**: This option is applicable to full app embedding only. + * @version SDK: 1.38.0 | ThoughtSpot: 10.9.0.cl + */ +var ListPageColumns; +(function (ListPageColumns) { + /** + * Favorites + */ + ListPageColumns["Favorites"] = "FAVOURITE"; + /** + * Favourite Use {@link ListPageColumns.Favorites} instead. + * @deprecated This option is deprecated. + */ + ListPageColumns["Favourite"] = "FAVOURITE"; + /** + * Tags + */ + ListPageColumns["Tags"] = "TAGS"; + /** + * Author + */ + ListPageColumns["Author"] = "AUTHOR"; + /** + * Last viewed/Last modified + */ + ListPageColumns["DateSort"] = "DATE_SORT"; + /** + * Share + */ + ListPageColumns["Share"] = "SHARE"; + /** + * Verified badge/column + */ + ListPageColumns["Verified"] = "VERIFIED"; +})(ListPageColumns || (ListPageColumns = {})); +/** + * Event types emitted by the embedded ThoughtSpot application. + * + * To add an event listener use the corresponding + * {@link LiveboardEmbed.on} or {@link AppEmbed.on} or {@link SearchEmbed.on} method. + * @example + * ```js + * import { EmbedEvent } from '@thoughtspot/visual-embed-sdk'; + * // Or + * // const { EmbedEvent } = window.tsembed; + * + * // create the liveboard embed. + * + * liveboardEmbed.on(EmbedEvent.Drilldown, (drilldown) => { + * console.log('Drilldown event', drilldown); + * })); + * ``` + * + * If you are using React components for embedding, you can register to any + * events from the `EmbedEvent` list by using the `on` convention. + * For example,`onAlert`, `onCopyToClipboard` and so on. + * @example + * ```js + * // ... + * const MyComponent = ({ dataSources }) => { + * const onLoad = () => { + * console.log(EmbedEvent.Load, {}); + * }; + * + * return ( + * + * ); + * }; + * ``` + * @group Events + */ +var EmbedEvent; +(function (EmbedEvent) { + /** + * Rendering has initialized. + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Init, showLoader) + * //show a loader + * function showLoader() { + * document.getElementById("loader"); + * } + * ``` + * @returns timestamp - The timestamp when the event was generated. + */ + EmbedEvent["Init"] = "init"; + /** + * Authentication has either succeeded or failed. + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.AuthInit, payload => { + * console.log('AuthInit', payload); + * }) + * ``` + * @returns isLoggedIn - A Boolean specifying whether authentication was successful. + */ + EmbedEvent["AuthInit"] = "authInit"; + /** + * The embed object container has loaded. + * @returns timestamp - The timestamp when the event was generated. + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Load, hideLoader) + * //hide loader + * function hideLoader() { + * document.getElementById("loader"); + * } + * ``` + */ + EmbedEvent["Load"] = "load"; + /** + * Data pertaining to an Answer, Liveboard or Spotter visualization is received. + * The event payload includes the raw data of the object. + * @return data - Answer of Liveboard data + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Data, payload => { + * console.log('data', payload); + * }) + * ``` + * @important + */ + EmbedEvent["Data"] = "data"; + /** + * Search query has been updated by the user. + * @version SDK: 1.4.0 | ThoughtSpot: ts7.sep.cl, 8.4.1.sw + * @example + * ```js + * searchEmbed.on(EmbedEvent.QueryChanged, payload => console.log('data', payload)) + * ``` + */ + EmbedEvent["QueryChanged"] = "queryChanged"; + /** + * A drill-down operation has been performed. + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @returns additionalFilters - Any additional filters applied + * @returns drillDownColumns - The columns on which drill down was performed + * @returns nonFilteredColumns - The columns that were not filtered + * @example + * ```js + * searchEmbed.on(EmbedEvent.DrillDown, { + * points: { + * clickedPoint, + * selectedPoints: selectedPoint + * }, + * autoDrillDown: true, + * }) + * ``` + * In this example, `VizPointDoubleClick` event is used for + * triggering the `DrillDown` event when an area or specific + * data point on a table or chart is double-clicked. + * @example + * ```js + * searchEmbed.on(EmbedEvent.VizPointDoubleClick, (payload) => { + * console.log(payload); + * const clickedPoint = payload.data.clickedPoint; + * const selectedPoint = payload.data.selectedPoints; + * console.log('>>> called', clickedPoint); + * embed.trigger(HostEvent.DrillDown, { + * points: { + * clickedPoint, + * selectedPoints: selectedPoint + * }, + * autoDrillDown: true, + * }) + * }) + * ``` + */ + EmbedEvent["Drilldown"] = "drillDown"; + /** + * One or more data sources have been selected. + * @returns dataSourceIds - the list of data sources + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * searchEmbed.on(EmbedEvent.DataSourceSelected, payload => { + * console.log('DataSourceSelected', payload); + * }) + * ``` + */ + EmbedEvent["DataSourceSelected"] = "dataSourceSelected"; + /** + * One or more data columns have been selected. + * @returns columnIds - the list of columns + * @version SDK: 1.10.0 | ThoughtSpot: 8.2.0.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.AddRemoveColumns, payload => { + * console.log('AddRemoveColumns', payload); + * }) + * ``` + */ + EmbedEvent["AddRemoveColumns"] = "addRemoveColumns"; + /** + * A custom action has been triggered. + * @returns actionId - ID of the custom action + * @returns payload {@link CustomActionPayload} - Response payload with the + * Answer or Liveboard data + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.customAction, payload => { + * const data = payload.data; + * if (data.id === 'insert Custom Action ID here') { + * console.log('Custom Action event:', data.embedAnswerData); + * } + * }) + * ``` + */ + EmbedEvent["CustomAction"] = "customAction"; + /** + * Listen to double click actions on a visualization. + * @return ContextMenuInputPoints - Data point that is double-clicked + * @version SDK: 1.5.0 | ThoughtSpot: ts7.oct.cl, 7.2.1 + * @example + * ```js + * LiveboardEmbed.on(EmbedEvent.VizPointDoubleClick, payload => { + * console.log('VizPointDoubleClick', payload); + * }) + * ``` + */ + EmbedEvent["VizPointDoubleClick"] = "vizPointDoubleClick"; + /** + * Listen to clicks on a visualization in a Liveboard or Search result. + * @return viz, clickedPoint - metadata about the point that is clicked + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @important + * @example + * ```js + * embed.on(EmbedEvent.VizPointClick, ({data}) => { + * console.log( + * data.vizId, // viz id + * data.clickedPoint.selectedAttributes[0].value, + * data.clickedPoint.selectedAttributes[0].column.name, + * data.clickedPoint.selectedMeasures[0].value, + * data.clickedPoint.selectedMeasures[0].column.name, + * ) + * }); + * ``` + */ + EmbedEvent["VizPointClick"] = "vizPointClick"; + /** + * Fired when an error occurs in the embedded component. + * + * **Important:** This event fires for many reasons — including internal + * validation warnings (e.g. `HOST_EVENT_VALIDATION`), configuration issues, + * and transient errors that ThoughtSpot already handles gracefully inside the + * iframe. **Do not call `embed.destroy()` or unmount the embed component on + * every error.** Doing so will tear down the iframe and abort all in-flight + * requests, causing the embed to fail entirely. + * + * Only treat the following codes as unrecoverable: + * - `INIT_ERROR` — SDK was not initialised before render + * - `LOGIN_FAILED` — authentication could not be completed + * + * All other error codes should be logged and inspected, not acted upon + * destructively. + * + * **Note:** There is currently no dedicated event for a true unrecoverable + * crash. A future `EmbedEvent.FatalError` event is planned to give customers + * a clean signal for when the embed cannot recover and needs to be torn down. + * + * Error types include: + * `API` - API call failure. + * `FULLSCREEN` - Error when presenting a Liveboard in full screen mode. + * `VALIDATION_ERROR` - Internal host event or configuration validation warning. + * + * For more information, see https://developers.thoughtspot.com/docs/events-app-integration#errorType + * @returns error - An error object or message + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * // Recommended pattern — only destroy on truly fatal errors + * embed.on(EmbedEvent.Error, (error) => { + * const FATAL_CODES = ['INIT_ERROR', 'LOGIN_FAILED']; + * if (FATAL_CODES.includes(error.data?.code)) { + * embed.destroy(); + * return; + * } + * // Log all other errors — do not destroy + * console.warn('Embed error (non-fatal):', error); + * }); + * ``` + * @example + * ```js + * // API error + * SearchEmbed.on(EmbedEvent.Error, (error) => { + * console.log(error); + * // { errorType: "API", message: '...', code: '...' } + * }); + * ``` + */ + EmbedEvent["Error"] = "Error"; + /** + * The embedded object has sent an alert. + * @returns alert - An alert object + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 8.4.1.sw + * @example + * ```js + * searchEmbed.on(EmbedEvent.Alert) + * ``` + */ + EmbedEvent["Alert"] = "alert"; + /** + * The ThoughtSpot authentication session has expired. + * @version SDK: 1.4.0 | ThoughtSpot: ts7.sep.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.AuthExpire, showAuthExpired) + * //show auth expired banner + * function showAuthExpired() { + * document.getElementById("authExpiredBanner"); + * } + * ``` + */ + EmbedEvent["AuthExpire"] = "ThoughtspotAuthExpired"; + /** + * ThoughtSpot failed to validate the auth session. + * @hidden + */ + EmbedEvent["AuthFailure"] = "ThoughtspotAuthFailure"; + /** + * ThoughtSpot failed to re validate the auth session. + * @hidden + */ + EmbedEvent["IdleSessionTimeout"] = "IdleSessionTimeout"; + /** + * ThoughtSpot failed to validate the auth session. + * @hidden + */ + EmbedEvent["AuthLogout"] = "ThoughtspotAuthLogout"; + /** + * The height of the embedded Liveboard or visualization has been computed. + * @returns data - The height of the embedded Liveboard or visualization + * @hidden + */ + EmbedEvent["EmbedHeight"] = "EMBED_HEIGHT"; + /** + * The center of visible iframe viewport is calculated. + * @returns data - The center of the visible Iframe viewport. + * @hidden + */ + EmbedEvent["EmbedIframeCenter"] = "EmbedIframeCenter"; + /** + * Emitted when the **Get Data** action is initiated. + * Applicable to `SearchBarEmbed` only. + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + * @example + * ```js + * searchbarEmbed.on(EmbedEvent.GetDataClick) + * .then(data => { + * console.log('Answer Data:', data); + * }) + * ``` + */ + EmbedEvent["GetDataClick"] = "getDataClick"; + /** + * Detects the route change. + * @version SDK: 1.7.0 | ThoughtSpot: 8.0.0.cl, 8.4.1.sw + * @example + * ```js + * searchEmbed.on(EmbedEvent.RouteChange, payload => + * console.log('data', payload)) + * ``` + */ + EmbedEvent["RouteChange"] = "ROUTE_CHANGE"; + /** + * The v1 event type for Data + * @hidden + */ + EmbedEvent["V1Data"] = "exportVizDataToParent"; + /** + * Emitted when the embed does not have cookie access. This happens + * when third-party cookies are blocked by Safari or other + * web browsers. `NoCookieAccess` can trigger. + * @example + * ```js + * appEmbed.on(EmbedEvent.NoCookieAccess) + * ``` + * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 7.2.1.sw + */ + EmbedEvent["NoCookieAccess"] = "noCookieAccess"; + /** + * Emitted when SAML is complete + * @private + * @hidden + */ + EmbedEvent["SAMLComplete"] = "samlComplete"; + /** + * Emitted when any modal is opened in the app + * @version SDK: 1.6.0 | ThoughtSpot: ts8.nov.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.DialogOpen, payload => { + * console.log('dialog open', payload); + * }) + * ``` + */ + EmbedEvent["DialogOpen"] = "dialog-open"; + /** + * Emitted when any modal is closed in the app + * @version SDK: 1.6.0 | ThoughtSpot: ts8.nov.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.DialogClose, payload => { + * console.log('dialog close', payload); + * }) + * ``` + */ + EmbedEvent["DialogClose"] = "dialog-close"; + /** + * Emitted when the Liveboard shell loads. + * You can use this event as a hook to trigger + * other events on the rendered Liveboard. + * @version SDK: 1.9.1 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.LiveboardRendered, payload => { + console.log('Liveboard is rendered', payload); + }) + * ``` + * The following example shows how to trigger + * `SetVisibleVizs` event using LiveboardRendered embed event: + * @example + * ```js + * const embedRef = useEmbedRef(); + * const onLiveboardRendered = () => { + * embed.trigger(HostEvent.SetVisibleVizs, ['viz1', 'viz2']); + * }; + * ``` + */ + EmbedEvent["LiveboardRendered"] = "PinboardRendered"; + /** + * Emits all events. + * @version SDK: 1.10.0 | ThoughtSpot: 8.2.0.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.ALL, payload => { + * console.log('Embed Events', payload) + * }) + * ``` + */ + EmbedEvent["ALL"] = "*"; + /** + * Emitted when an Answer is saved in the app. + * Use start:true to subscribe to when save is initiated, or end:true to subscribe to when save is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //Emit when action starts + * searchEmbed.on(EmbedEvent.Save, payload => { + * console.log('Save', payload) + * }, { + * start: true + * }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.Save, payload => { + * console.log('Save', payload) + * }) + * ``` + */ + EmbedEvent["Save"] = "save"; + /** + * Emitted when the download action is triggered on an Answer. + * + * **Note**: This event is deprecated in v1.21.0. + * To fire an event when a download action is initiated on a chart or table, + * use `EmbedEvent.DownloadAsPng`, `EmbedEvent.DownloadAsPDF`, + * `EmbedEvent.DownloadAsCSV`, or `EmbedEvent.DownloadAsXLSX` + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Download, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }) + * ``` + */ + EmbedEvent["Download"] = "download"; + /** + * Emitted when the download action is triggered on an Answer. + * Use start:true to subscribe to when download is initiated, or end:true to + * subscribe to when download is completed. Default is end:true. + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.4.0.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.DownloadAsPng, payload => { + * console.log('download PNG', payload)}, {start: true }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.DownloadAsPng, payload => { + * console.log('download PNG', payload)}) + * ``` + */ + EmbedEvent["DownloadAsPng"] = "downloadAsPng"; + /** + * Emitted when the Download as PDF action is triggered on an Answer + * Use start:true to subscribe to when download as PDF is initiated, or end:true to + * subscribe to when download as PDF is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.DownloadAsPdf, payload => { + * console.log('download PDF', payload)}, {start: true }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.DownloadAsPdf, payload => { + * console.log('download PDF', payload)}) + * ``` + */ + EmbedEvent["DownloadAsPdf"] = "downloadAsPdf"; + /** + * Emitted when the Download as CSV action is triggered on an Answer. + * Use start:true to subscribe to when download as CSV is initiated, or end:true to + * subscribe to when download as CSV is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.DownloadAsCSV, payload => { + * console.log('download CSV', payload)}, {start: true }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.DownloadAsCSV, payload => { + * console.log('download CSV', payload)}) + * ``` + */ + EmbedEvent["DownloadAsCsv"] = "downloadAsCsv"; + /** + * Emitted when the Download as XLSX action is triggered on an Answer. + * Use start:true to subscribe to when download as XLSX is initiated, or end:true to + * subscribe to when download as XLSX is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.DownloadAsXlsx, payload => { + * console.log('download Xlsx', payload)}, { start: true }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.DownloadAsXlsx, payload => { + * console.log('download Xlsx', payload)}) + * ``` + */ + EmbedEvent["DownloadAsXlsx"] = "downloadAsXlsx"; + /** + * Emitted when the Download Liveboard as Continuous PDF action is triggered + * on a Liveboard. + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.DownloadLiveboardAsContinuousPDF, payload => { + * console.log('download liveboard as continuous PDF', payload)}) + * ``` + */ + EmbedEvent["DownloadLiveboardAsContinuousPDF"] = "downloadLiveboardAsContinuousPDF"; + /** + * Emitted when an Answer is deleted in the app + * Use start:true to subscribe to when delete is initiated, or end:true to subscribe + * to when delete is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * appEmbed.on(EmbedEvent.AnswerDelete, payload => { + * console.log('delete answer', payload)}, {start: true }) + * //trigger when action is completed + * appEmbed.on(EmbedEvent.AnswerDelete, payload => { + * console.log('delete answer', payload)}) + * ``` + */ + EmbedEvent["AnswerDelete"] = "answerDelete"; + /** + * Emitted when the AI Highlights action is triggered on a Liveboard + * @version SDK: 1.44.0 | ThoughtSpot: 10.15.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.AIHighlights, (payload) => { + * console.log('AI Highlights', payload); + * }) + * ``` + */ + EmbedEvent["AIHighlights"] = "AIHighlights"; + /** + * Emitted when a user initiates the Pin action to + * add an Answer to a Liveboard. + * Use start:true to subscribe to when pin is initiated, or end:true to subscribe to + * when pin is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.Pin, payload => { + * console.log('pin', payload) + * }, { + * start: true + * }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.Pin, payload => { + * console.log('pin', payload) + * }) + * ``` + */ + EmbedEvent["Pin"] = "pin"; + /** + * Emitted when SpotIQ analysis is triggered + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.SpotIQAnalyze, payload => { + * console.log('SpotIQAnalyze', payload) + * }, { + * start: true + * }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.SpotIQAnalyze, payload => { + * console.log('SpotIQ analyze', payload) + * }) + * ``` + */ + EmbedEvent["SpotIQAnalyze"] = "spotIQAnalyze"; + /** + * Emitted when a user shares an object with another user or group + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.Share, payload => { + * console.log('Share', payload) + * }, { + * start: true + * }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.Share, payload => { + * console.log('Share', payload) + * }) + * ``` + */ + EmbedEvent["Share"] = "share"; + /** + * Emitted when a user clicks the **Include** action to include a specific value or + * data on a chart or table. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.DrillInclude, payload => { + * console.log('Drill include', payload); + * }) + * ``` + */ + EmbedEvent["DrillInclude"] = "context-menu-item-include"; + /** + * Emitted when a user clicks the **Exclude** action to exclude a specific value or + * data on a chart or table + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.DrillExclude, payload => { + * console.log('Drill exclude', payload); + * }) + * ``` + */ + EmbedEvent["DrillExclude"] = "context-menu-item-exclude"; + /** + * Emitted when a column value is copied in the embedded app. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * searchEmbed.on(EmbedEvent.CopyToClipboard, payload => { + * console.log('copy to clipboard', payload); + * }) + * ``` + */ + EmbedEvent["CopyToClipboard"] = "context-menu-item-copy-to-clipboard"; + /** + * Emitted when a user clicks the **Update TML** action on + * embedded Liveboard. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.UpdateTML) + * }) + * ``` + */ + EmbedEvent["UpdateTML"] = "updateTSL"; + /** + * Emitted when a user clicks the **Edit TML** action + * on an embedded Liveboard. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.EditTML, payload => { + * console.log('Edit TML', payload); + * }) + * ``` + */ + EmbedEvent["EditTML"] = "editTSL"; + /** + * Emitted when the **Export TML** action is triggered on an + * an embedded object in the app + * Use start:true to subscribe to when export is initiated, or end:true to subscribe + * to when export is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * searchEmbed.on(EmbedEvent.ExportTML, payload => { + * console.log('Export TML', payload)}, { start: true }) + * //emit when action ends + * searchEmbed.on(EmbedEvent.ExportTML, payload => { + * console.log('Export TML', payload)}) + * ``` + */ + EmbedEvent["ExportTML"] = "exportTSL"; + /** + * Emitted when an Answer is saved as a View. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * appEmbed.on(EmbedEvent.SaveAsView, payload => { + * console.log('View', payload); + * }) + * ``` + */ + EmbedEvent["SaveAsView"] = "saveAsView"; + /** + * Emitted when the user creates a copy of an Answer. + * Use start:true to subscribe to when copy and edit is initiated, or end:true to + * subscribe to when copy and edit is completed. Default is end:true. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * //emit when action starts + * appEmbed.on(EmbedEvent.CopyAEdit, payload => { + * console.log('Copy and edit', payload)}, {start: true }) + * //emit when action ends + * appEmbed.on(EmbedEvent.CopyAEdit, payload => { + * console.log('Copy and edit', payload)}) + * ``` + */ + EmbedEvent["CopyAEdit"] = "copyAEdit"; + /** + * Emitted when a user clicks *Show underlying data* on an Answer. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.ShowUnderlyingData, payload => { + * console.log('show data', payload); + * }) + * ``` + */ + EmbedEvent["ShowUnderlyingData"] = "showUnderlyingData"; + /** + * Emitted when an Answer is switched to a chart or table view. + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @example + * ```js + * searchEmbed.on(EmbedEvent.AnswerChartSwitcher, payload => { + * console.log('switch view', payload); + * }) + * ``` + */ + EmbedEvent["AnswerChartSwitcher"] = "answerChartSwitcher"; + /** + * Internal event to communicate the initial settings back to the ThoughtSpot app + * @hidden + */ + EmbedEvent["APP_INIT"] = "appInit"; + /** + * Internal event to clear the cached info + * @hidden + */ + EmbedEvent["CLEAR_INFO_CACHE"] = "clearInfoCache"; + /** + * Emitted when a user clicks **Show Liveboard details** on a Liveboard + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.LiveboardInfo, payload => { + * console.log('Liveboard details', payload); + * }) + * ``` + */ + EmbedEvent["LiveboardInfo"] = "pinboardInfo"; + /** + * Emitted when a user clicks on the Favorite icon on a Liveboard + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.AddToFavorites, payload => { + * console.log('favorites', payload); + * }) + * ``` + */ + EmbedEvent["AddToFavorites"] = "addToFavorites"; + /** + * Emitted when a user clicks **Schedule** on a Liveboard + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Schedule, payload => { + * console.log('Liveboard schedule', payload); + * }) + * ``` + */ + EmbedEvent["Schedule"] = "subscription"; + /** + * Emitted when a user clicks **Edit** on a Liveboard or visualization + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Edit, payload => { + * console.log('Liveboard edit', payload); + * }) + * ``` + */ + EmbedEvent["Edit"] = "edit"; + /** + * Emitted when a user clicks *Make a copy* on a Liveboard + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.MakeACopy, payload => { + * console.log('Copy', payload); + * }) + * ``` + */ + EmbedEvent["MakeACopy"] = "makeACopy"; + /** + * Emitted when a user clicks **Present** on a Liveboard or visualization + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Present) + * ``` + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Present, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * }) + * ``` + */ + EmbedEvent["Present"] = "present"; + /** + * Emitted when a user clicks **Delete** on a visualization + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Delete, + * {vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + */ + EmbedEvent["Delete"] = "delete"; + /** + * Emitted when a user clicks Manage schedules on a Liveboard + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SchedulesList) + * ``` + */ + EmbedEvent["SchedulesList"] = "schedule-list"; + /** + * Emitted when a user clicks **Cancel** in edit mode on a Liveboard + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Cancel) + * ``` + */ + EmbedEvent["Cancel"] = "cancel"; + /** + * Emitted when a user clicks **Explore** on a visualization + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.Explore, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + */ + EmbedEvent["Explore"] = "explore"; + /** + * Emitted when a user clicks **Copy link** action on a visualization. + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.CopyLink, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + */ + EmbedEvent["CopyLink"] = "embedDocument"; + /** + * Emitted when a user interacts with cross filters on a + * visualization or Liveboard. + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.0.sw + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.CrossFilterChanged, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + */ + EmbedEvent["CrossFilterChanged"] = "cross-filter-changed"; + /** + * Emitted when a user right clicks on a visualization (chart or table) + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.0.sw + * @example + * ```js + * LiveboardEmbed.on(EmbedEvent.VizPointRightClick, payload => { + * console.log('VizPointClick', payload) + * }) + * ``` + */ + EmbedEvent["VizPointRightClick"] = "vizPointRightClick"; + /** + * Emitted when a user clicks **Insert to slide** on a visualization + * @hidden + */ + EmbedEvent["InsertIntoSlide"] = "insertInToSlide"; + /** + * Emitted when a user changes any filter on a Liveboard. + * Returns filter type and name, column name and ID, and runtime + * filter details. + * @example + * + * ```js + * LiveboardEmbed.on(EmbedEvent.FilterChanged, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.23.0 | ThoughtSpot: 9.4.0.cl, 9.5.0.sw + */ + EmbedEvent["FilterChanged"] = "filterChanged"; + /** + * Emitted when a user updates a connection on the **Data** page + * @version SDK: 1.27.0 | ThoughtSpot: 9.8.0.cl, 9.8.0.sw + */ + EmbedEvent["UpdateConnection"] = "updateConnection"; + /** + * Emitted when a user updates a connection on the **Data** page + * @version SDK: 1.27.0 | ThoughtSpot: 9.8.0.cl, 9.8.0.sw + */ + EmbedEvent["CreateConnection"] = "createConnection"; + /** + * Emitted when name, status (private or public) or filter values of a + * Personalised view is updated. + * This event is deprecated. Use {@link EmbedEvent.UpdatePersonalizedView} instead. + * @returns viewName: string + * @returns viewId: string + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + EmbedEvent["UpdatePersonalisedView"] = "updatePersonalisedView"; + /** + * Emitted when name, status (private or public) or filter values of a + * Personalized view is updated. + * @returns viewName: string + * @returns viewId: string + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + EmbedEvent["UpdatePersonalizedView"] = "updatePersonalisedView"; + /** + * Emitted when a Personalised view is saved. + * This event is deprecated. Use {@link EmbedEvent.SavePersonalizedView} instead. + * @returns viewName: string + * @returns viewId: string + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + EmbedEvent["SavePersonalisedView"] = "savePersonalisedView"; + /** + * Emitted when a Personalized view is saved. + * @returns viewName: string + * @returns viewId: string + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + EmbedEvent["SavePersonalizedView"] = "savePersonalisedView"; + /** + * Emitted when a Liveboard is reset. + * @returns viewName: string + * @returns viewId: string + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + */ + EmbedEvent["ResetLiveboard"] = "resetLiveboard"; + /** + * Emitted when a PersonalisedView is deleted. + * This event is deprecated. Use {@link EmbedEvent.DeletePersonalizedView} instead. + * @returns views: string[] + * @returns liveboardId: string + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + EmbedEvent["DeletePersonalisedView"] = "deletePersonalisedView"; + /** + * Emitted when a PersonalizedView is deleted. + * @returns views: string[] + * @returns liveboardId: string + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + EmbedEvent["DeletePersonalizedView"] = "deletePersonalisedView"; + /** + * Emitted when a user selects a different Personalized View or + * resets to the original/default view on a Liveboard. + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.ChangePersonalizedView, (data) => { + * console.log(data.viewName); // 'Q4 Revenue' or 'Original View' + * console.log(data.viewId); // '2a021a12-...' or null (default) + * console.log(data.liveboardId); // 'abc123...' + * console.log(data.isPublic); // true | false + * }) + * ``` + * @returns viewName: string - Name of the selected view, + * or 'Original View' when reset to default. + * @returns viewId: string | null - GUID of the selected view, + * or null when reset to default. + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + EmbedEvent["ChangePersonalizedView"] = "changePersonalisedView"; + /** + * Emitted when a user creates a Worksheet. + * @version SDK: 1.27.0 | ThoughtSpot: 9.8.0.cl, 9.8.0.sw + */ + EmbedEvent["CreateWorksheet"] = "createWorksheet"; + /** + * Emitted when the *Ask Sage* is initialized. + * @returns viewName: string + * @returns viewId: string + * @returns liveboardId: string + * @returns isPublic: boolean + * @version SDK: 1.29.0 | ThoughtSpot Cloud: 9.12.0.cl + */ + EmbedEvent["AskSageInit"] = "AskSageInit"; + /** + * Emitted when a Liveboard or visualization is renamed. + * @version SDK: 1.28.0 | ThoughtSpot: 9.10.5.cl, 10.1.0.sw + */ + EmbedEvent["Rename"] = "rename"; + /** + * + * This event allows developers to intercept search execution + * and implement logic that decides whether Search Data should return + * data or block the search operation. + * + * **Prerequisite**: Set`isOnBeforeGetVizDataInterceptEnabled` to `true` + * to ensure that `EmbedEvent.OnBeforeGetVizDataIntercept` is emitted + * when the embedding application user tries to run a search query. + * + * This framework applies only to `AppEmbed` and `SearchEmbed`. + * @param - Includes the following parameters: + * - `payload`: The payload received from the embed related to the Data API call. + * - `responder`: Contains elements that let developers define whether ThoughtSpot + * will run or block the search operation, and if blocked, which error message to + * provide. + * - `execute` - When `execute` returns `true`, the search is run. + * When `execute` returns `false`, the search is not executed. + * - `error` - Developers can customize the user-facing error message when `execute` + * is `false` by using the `error` parameters in `responder`. + * - `errorText` - The error message text shown to the user. + * @version SDK: 1.29.0 | ThoughtSpot: 10.3.0.cl + * @example + * + * This example blocks search operation and returns a custom error message: + * ```js + * embed.on(EmbedEvent.OnBeforeGetVizDataIntercept, (payload, responder) => { + * responder({ + * data: { + * execute: false, + * error: { + * // Provide a custom error message to explain why the search did not run. + * errorText: 'This search query cannot be run. Please contact your administrator for more details.', + * }, + * }, + * }); + * }) + * ``` + * @example + * + * This example allows the search operation to run + * unless the query contains both `sales` and `county`, + * and returns a custom error message if the query is rejected: + * ```js + * embed.on(EmbedEvent.OnBeforeGetVizDataIntercept, (payload, responder) => { + * // Record the search query submitted by the end user. + * const query = payload.data.data.answer.search_query; + * + * responder({ + * data: { + * // Returns true as long as the query does not include both `sales` and `county`. + * execute: !(query.includes('sales') && query.includes('county')), + * error: { + * // Provide a custom error message when the query is blocked by your logic. + * errorText: + * "You can't use this query: " + * + query + * + ". The 'sales' measure can never be used at the 'county' level. " + * + "Please try another measure or remove 'county' from your search.", + * }, + * }, + * }); + * }) + * ``` + */ + EmbedEvent["OnBeforeGetVizDataIntercept"] = "onBeforeGetVizDataIntercept"; + /** + * Emitted when parameter changes in an Answer + * or Liveboard. + * ```js + * liveboardEmbed.on(EmbedEvent.ParameterChanged, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.29.0 | ThoughtSpot: 10.3.0.cl + */ + EmbedEvent["ParameterChanged"] = "parameterChanged"; + /** + * Emits when a table visualization is rendered in + * the ThoughtSpot embedded app. + * + * You can also use this event as a hook to trigger host events + * such as `HostEvent.TransformTableVizData` on the table visualization. + * The event payload contains the data used in the rendered table. + * You can extract the relevant data from the payload + * stored in `payload.data.data.columnDataLite`. + * + * `columnDataLite` is a multidimensional array that contains + * data values for each column, which was used in the query to + * generate the table visualization. To find and modify specific cell data, + * you can either loop through the array or directly access a cell if + * you know its position and data index. + * + * In the following code sample, the first cell in the first column + * (`columnDataLite[0].dataValue[0]`) is set to `new fob`. + * Note that any changes made to the data in the payload will only update the + * visual presentation and do not affect the underlying data. + * To persist data value modifications after a reload or during chart + * interactions such as drill down, ensure that the modified + * payload in the `columnDataLite` is passed on to + * `HostEvent.TransformTableVizData` and trigger an update to + * the table visualization. + * + * If the Row-Level Security (RLS) rules are applied on the + * Model, exercise caution when changing column + * or table cell values to maintain data security. + * + * @example + * ```js + * searchEmbed.on(EmbedEvent.TableVizRendered, (payload) => { + * console.log(payload); + * const columnDataLite = payload.data.data.columnDataLite; + * columnDataLite[0].dataValue[0]="new fob"; + * console.log('>>> new Data', columnDataLite); + * searchEmbed.trigger(HostEvent.TransformTableVizData, columnDataLite); + * }) + * ``` + * @version SDK: 1.37.0 | ThoughtSpot: 10.8.0.cl + */ + EmbedEvent["TableVizRendered"] = "TableVizRendered"; + /** + * Emitted when the liveboard is created from pin modal or Liveboard list page. + * You can use this event as a hook to trigger + * other events on liveboard creation. + * + * ```js + * liveboardEmbed.on(EmbedEvent.CreateLiveboard, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.37.0 | ThoughtSpot: 10.8.0.cl + */ + EmbedEvent["CreateLiveboard"] = "createLiveboard"; + /** + * Emitted when a user creates a Model. + * @version SDK: 1.37.0 | ThoughtSpot: 10.8.0.cl + */ + EmbedEvent["CreateModel"] = "createModel"; + /** + * @hidden + * Emitted when a user exits present mode. + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + EmbedEvent["ExitPresentMode"] = "exitPresentMode"; + /** + * Emitted when a user requests the full height lazy load data. + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + * @hidden + */ + EmbedEvent["RequestVisibleEmbedCoordinates"] = "requestVisibleEmbedCoordinates"; + /** + * Emitted when Spotter response is text data + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterData, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + EmbedEvent["SpotterData"] = "SpotterData"; + /** + * Emitted when user opens up the data source preview modal in Spotter embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.PreviewSpotterData, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + EmbedEvent["PreviewSpotterData"] = "PreviewSpotterData"; + /** + * Emitted when user opens up the Add to Coaching modal on any visualization in Spotter Embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.AddToCoaching, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.45.0 | ThoughtSpot: 26.2.0.cl + */ + EmbedEvent["AddToCoaching"] = "addToCoaching"; + /** + * Emitted when user opens up the data model instructions modal in Spotter embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.DataModelInstructions, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + */ + EmbedEvent["DataModelInstructions"] = "DataModelInstructions"; + /** + * Emitted when the Spotter query is triggered in Spotter embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterQueryTriggered, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + EmbedEvent["SpotterQueryTriggered"] = "SpotterQueryTriggered"; + /** + * Emitted when the last Spotter query is edited in Spotter embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.LastPromptEdited, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + EmbedEvent["LastPromptEdited"] = "LastPromptEdited"; + /** + * Emitted when the last Spotter query is deleted in Spotter embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.LastPromptDeleted, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + EmbedEvent["LastPromptDeleted"] = "LastPromptDeleted"; + /** + * Emitted when the conversation is reset in Spotter embed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.ResetSpotterConversation, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + EmbedEvent["ResetSpotterConversation"] = "ResetSpotterConversation"; + /** + * Emitted when the *Spotter* is initialized. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterInit, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.41.0 | ThoughtSpot: 10.12.0.cl + */ + EmbedEvent["SpotterInit"] = "spotterInit"; + /** + * Emitted when a *Spotter* conversation has been successfully created. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterLoadComplete, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.44.0 | ThoughtSpot: 26.2.0.cl + */ + EmbedEvent["SpotterLoadComplete"] = "spotterLoadComplete"; + /** + * @hidden + * Triggers when the embed listener is ready to receive events. + * This is used to trigger events after the embed container is loaded. + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.EmbedListenerReady, () => { + * console.log('EmbedListenerReady'); + * }) + * ``` + */ + EmbedEvent["EmbedListenerReady"] = "EmbedListenerReady"; + /** + * Emitted when the organization is switched. + * @example + * ```js + * appEmbed.on(EmbedEvent.OrgSwitched, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.41.0 | ThoughtSpot: 10.12.0.cl + */ + EmbedEvent["OrgSwitched"] = "orgSwitched"; + /** + * Emitted when the user intercepts a URL. + * + * Supported on all embed types. + * + * @example + * + * ```js + * embed.on(EmbedEvent.ApiIntercept, (payload, responder) => { + * console.log('payload', payload); + * responder({ + * data: { + * execute: false, + * error: { + * errorText: 'Error Occurred', + * } + * } + * }) + * }) + * ``` + * + * ```js + * // We can also send a response for the intercepted api + * embed.on(EmbedEvent.ApiIntercept, (payload, responder) => { + * console.log('payload', payload); + * responder({ + * data: { + * execute: false, + * response: { + * body: { + * data: { + * // Some api response + * }, + * } + * } + * } + * }) + * }) + * + * // here embed will use the response from the responder as the response for the api + * ``` + * + * ```js + * // We can also send error in response for the intercepted api + * embed.on(EmbedEvent.ApiIntercept, (payload, responder) => { + * console.log('payload', payload); + * responder({ + * data: { + * execute: false, + * response: { + * body: { + * errors: [{ + * title: 'Error Occurred', + * description: 'Error Description', + * isUserError: true, + * }], + * data: {}, + * }, + * } + * } + * }) + * }) + * ``` + * @version SDK: 1.43.0 | ThoughtSpot: 10.15.0.cl + */ + EmbedEvent["ApiIntercept"] = "ApiIntercept"; + /** + * Emitted when a Spotter conversation is renamed. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterConversationRenamed, (payload) => { + * console.log('Conversation renamed', payload); + * // payload: { convId: string, oldTitle: string, newTitle: string } + * }) + * ``` + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + */ + EmbedEvent["SpotterConversationRenamed"] = "spotterConversationRenamed"; + /** + * Emitted when a Spotter conversation is deleted. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterConversationDeleted, (payload) => { + * console.log('Conversation deleted', payload); + * // payload: { convId: string, title: string } + * }) + * ``` + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + */ + EmbedEvent["SpotterConversationDeleted"] = "spotterConversationDeleted"; + /** + * Emitted when a Spotter conversation is selected/clicked. + * @example + * ```js + * spotterEmbed.on(EmbedEvent.SpotterConversationSelected, (payload) => { + * console.log('Conversation selected', payload); + * // payload: { convId: string, title: string, worksheetId: string } + * }) + * ``` + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + */ + EmbedEvent["SpotterConversationSelected"] = "spotterConversationSelected"; + /** + * @hidden + * Emitted when the auth token is about to get expired and needs to be refreshed. + * @example + * ```js + * embed.on(EmbedEvent.RefreshAuthToken, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.45.2 | ThoughtSpot: 26.3.0.cl + */ + EmbedEvent["RefreshAuthToken"] = "RefreshAuthToken"; + /** + * Triggered whenever the page context changes, returning the current context along with the navigation stack. + * @example + * ```js + * embed.on(EmbedEvent.EmbedPageContextChanged, (payload) => { + * console.log('payload', payload); + * }) + * ``` + * @version SDK: 1.47.2 | ThoughtSpot: 26.3.0.cl + */ + EmbedEvent["EmbedPageContextChanged"] = "EmbedPageContextChanged"; + /** + * Represents a special embed event that is triggered whenever any host event is subscribed. + * + * You can listen to this event when you need to dispatch a host event during load or render, + * particularly in situations where timing issues may occur. + * + * @example + * ```js + * embed.on(`${HostEvent.Save} Subscribed`, () => { + * // make action + * }); + * ``` + * + * @example + * ```js + * embed.on(subscribedEvent(HostEvent.Save), () => { + * // make action + * }); + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.4.0.cl + */ + EmbedEvent["Subscribed"] = "Subscribed"; + /** + * Emitted when a user clicks the **Send Test Email** button in the + * Liveboard schedule modal. Requires `isSendNowLiveboardSchedulingEnabled` + * to be enabled. + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SendTestScheduleEmail, (payload) => { + * console.log('Send test email', payload); + * // payload: { liveboardId: string, sendToSelf: boolean } + * }) + * ``` + * @version SDK: 1.48.0 | ThoughtSpot Cloud: 26.5.0.cl + */ + EmbedEvent["SendTestScheduleEmail"] = "sendTestScheduleEmail"; + /** + * Emitted when the SpotterViz panel mounts in embed mode. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizInit, (payload) => { + * console.log('SpotterViz initialized', payload); + * // payload: { liveboardId: string } + * }) + * ``` + */ + EmbedEvent["SpotterVizInit"] = "SpotterVizInit"; + /** + * Emitted when the user submits a prompt in the SpotterViz panel. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizQueryTriggered, (payload) => { + * console.log('SpotterViz query triggered', payload); + * // payload: { query: string, sessionId: string } + * }) + * ``` + */ + EmbedEvent["SpotterVizQueryTriggered"] = "SpotterVizQueryTriggered"; + /** + * Emitted when the SpotterViz agent finishes responding. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizResponseComplete, (payload) => { + * console.log('SpotterViz response complete', payload); + * // payload: { sessionId: string, messageId: string } + * }) + * ``` + */ + EmbedEvent["SpotterVizResponseComplete"] = "SpotterVizResponseComplete"; + /** + * Emitted when a checkpoint is created in the SpotterViz panel. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizCheckpointCreated, (payload) => { + * console.log('SpotterViz checkpoint created', payload); + * // payload: { checkpointId: string, source: string, label: string } + * }) + * ``` + */ + EmbedEvent["SpotterVizCheckpointCreated"] = "SpotterVizCheckpointCreated"; + /** + * Emitted when a checkpoint is restored in the SpotterViz panel. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizCheckpointRestored, (payload) => { + * console.log('SpotterViz checkpoint restored', payload); + * // payload: { checkpointId: string, newGenNumber: number } + * }) + * ``` + */ + EmbedEvent["SpotterVizCheckpointRestored"] = "SpotterVizCheckpointRestored"; + /** + * Emitted when an error occurs in the SpotterViz panel. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizError, (payload) => { + * console.log('SpotterViz error', payload); + * }) + * ``` + */ + EmbedEvent["SpotterVizError"] = "SpotterVizError"; + /** + * Emitted when the SpotterViz panel is closed. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.SpotterVizClosed, (payload) => { + * console.log('SpotterViz panel closed', payload); + * }) + * ``` + */ + EmbedEvent["SpotterVizClosed"] = "SpotterVizClosed"; + /** + * Emitted when a user clicks the **Refresh** button in the + * Liveboard header. Requires `enableLiveboardDataCache` + * to be enabled. + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.RefreshLiveboardBrowserCache, (payload) => { + * console.log('Liveboard browser cache refreshed', payload); + * // payload: { liveboardId: string } + * }) + * ``` + * @version SDK: 1.49.0 | ThoughtSpot Cloud: 26.6.0.cl + */ + EmbedEvent["RefreshLiveboardBrowserCache"] = "refreshLiveboardBrowserCache"; +})(EmbedEvent || (EmbedEvent = {})); +/** + * Event types that can be triggered by the host application + * to the embedded ThoughtSpot app. + * + * To trigger an event use the corresponding + * {@link LiveboardEmbed.trigger} or {@link AppEmbed.trigger} or {@link + * SearchEmbed.trigger} method. + * @example + * ```js + * import { HostEvent } from '@thoughtspot/visual-embed-sdk'; + * // Or + * // const { HostEvent } = window.tsembed; + * + * // create the liveboard embed. + * + * liveboardEmbed.trigger(HostEvent.UpdateRuntimeFilters, [ + * { columnName: 'state', operator: RuntimeFilterOp.EQ, values: ["california"]} + * ]); + * ``` + * @example + * If using React components to embed, use the format shown in this example: + * + * ```js + * const selectVizs = () => { + * embedRef.current.trigger(HostEvent.SetVisibleVizs, [ + * "715e4613-c891-4884-be44-aa8d13701c06", + * "3f84d633-e325-44b2-be25-c6650e5a49cf" + * ]); + * }; + * ``` + * + * + * You can also attach an Embed event to a Host event to trigger + * a specific action as shown in this example: + * @example + * ```js + * const EmbeddedComponent = () => { + * const embedRef = useRef(null); // import { useRef } from react + * const onLiveboardRendered = () => { + * embedRef.current.trigger(HostEvent.SetVisibleVizs, ['viz1', 'viz2']); + * }; + * + * return ( + * + * ); + * } + * ``` + * + * **Context Parameter (SDK: 1.45.2+)** + * + * Starting from SDK version 1.45.2 | ThoughtSpot: 26.3.0.cl, you can optionally pass a + * `ContextType` as the third parameter to the `trigger` method to specify the context + * from which the event is triggered. This helps ThoughtSpot understand the current page + * context (Search, Answer, Liveboard, or Spotter) for better event handling. + * + * @example + * ```js + * import { HostEvent, ContextType } from '@thoughtspot/visual-embed-sdk'; + * + * // Trigger Pin event with Search context + * appEmbed.trigger(HostEvent.Pin, { + * vizId: "123", + * liveboardId: "456" + * }, ContextType.Search); + * ``` + * + * @group Events + */ +var HostEvent; +(function (HostEvent) { + /** + * Triggers a search operation with the search tokens specified in + * the search query string. + * Supported in `AppEmbed` and `SearchEmbed` deployments. + * Includes the following properties: + * @param - Includes the following keys: + * - `searchQuery`: Query string with search tokens. + * - `dataSources`: Data source GUID to search on. + * Although an array, only a single source is supported. + * - `execute`: Executes search and updates the existing query. + * @example + * ```js + * searchEmbed.trigger(HostEvent.Search, { + searchQuery: "[sales] by [item type]", + dataSources: ["cd252e5c-b552-49a8-821d-3eadaa049cca"], + execute: true + }); + * ``` + * @example + * ```js + * // Trigger search from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Search, { + * searchQuery: "[revenue] by [region]", + * dataSources: ["cd252e5c-b552-49a8-821d-3eadaa049cca"], + * execute: true + * }, ContextType.Search); + * ``` + */ + HostEvent["Search"] = "search"; + /** + * Triggers a drill on certain points of the specified column + * Includes the following properties: + * @param - Includes the following keys: + * - `points`: An object containing `selectedPoints` and/or `clickedPoint` + * to drill to. For example, `{ selectedPoints: [] }`. + * - `columnGuid`: Optional. GUID of the column to drill by. If not provided, + * it will auto drill by the configured column. + * - `autoDrillDown`: Optional. If `true`, the drill down will be done automatically + * on the most popular column. + * - `vizId` (TS >= 9.8.0): Optional. The GUID of the visualization to drill in case + * of a Liveboard. In Spotter embed, `vizId` refers to the Answer ID and is + * **required**. + * @example + * ```js + * searchEmbed.on(EmbedEvent.VizPointDoubleClick, (payload) => { + * console.log(payload); + * const clickedPoint = payload.data.clickedPoint; + * const selectedPoint = payload.data.selectedPoints; + * console.log('>>> called', clickedPoint); + * searchEmbed.trigger(HostEvent.DrillDown, { + * points: { + * clickedPoint, + * selectedPoints: selectedPoint + * }, + * autoDrillDown: true, + * }); + * }) + * ``` + * @example + * ```js + * // Works with TS 9.8.0 and above + * + * liveboardEmbed.on(EmbedEvent.VizPointDoubleClick, (payload) => { + * console.log(payload); + * const clickedPoint = payload.data.clickedPoint; + * const selectedPoint = payload.data.selectedPoints; + * console.log('>>> called', clickedPoint); + * liveboardEmbed.trigger(HostEvent.DrillDown, { + * points: { + * clickedPoint, + * selectedPoints: selectedPoint + * }, + * columnGuid: "", + * vizId: payload.data.vizId + * }); + * }) + * ``` + * @example + * ```js + * // Drill down from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.DrillDown, { + * points: { clickedPoint, selectedPoints }, + * autoDrillDown: true + * }, ContextType.Answer); + * ``` + * @example + * ```js + * // Drill down from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.DrillDown, { + * points: { clickedPoint, selectedPoints }, + * columnGuid: "column-guid" + * }, ContextType.Search); + * ``` + * @version SDK: 1.5.0 | ThoughtSpot: ts7.oct.cl, 7.2.1 + */ + HostEvent["DrillDown"] = "triggerDrillDown"; + /** + * Apply filters + * @hidden + */ + HostEvent["Filter"] = "filter"; + /** + * Reload the Answer or visualization + * @hidden + */ + HostEvent["Reload"] = "reload"; + /** + * Get iframe URL for the current embed view. + * @example + * ```js + * const url = embed.trigger(HostEvent.GetIframeUrl); + * console.log("iFrameURL",url); + * ``` + * @example + * ```js + * // Get iframe URL from specific context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * const url = await appEmbed.trigger(HostEvent.GetIframeUrl, {}, ContextType.Answer); + * console.log("iFrameURL", url); + * ``` + * @version SDK: 1.35.0 | ThoughtSpot: 10.4.0.cl + */ + HostEvent["GetIframeUrl"] = "GetIframeUrl"; + /** + * Display specific visualizations on a Liveboard. + * @param - An array of GUIDs of the visualization to show. The visualization IDs not passed + * in this parameter will be hidden. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SetVisibleVizs, [ + * '730496d6-6903-4601-937e-2c691821af3c', + * 'd547ec54-2a37-4516-a222-2b06719af726']) + * ``` + * @example + * ```js + * // Set visible vizs from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SetVisibleVizs, [ + * '730496d6-6903-4601-937e-2c691821af3c', + * 'd547ec54-2a37-4516-a222-2b06719af726' + * ], ContextType.Liveboard); + * ``` + * @version SDK: 1.6.0 | ThoughtSpot: ts8.nov.cl, 8.4.1.sw + */ + HostEvent["SetVisibleVizs"] = "SetPinboardVisibleVizs"; + /** + * Set a Liveboard tab as an active tab. + * @param - tabId - string of id of Tab to show + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SetActiveTab,{ + * tabId:'730496d6-6903-4601-937e-2c691821af3c' + * }) + * ``` + * @example + * ```js + * // Set active tab from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SetActiveTab, { + * tabId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.24.0 | ThoughtSpot: 9.5.0.cl, 9.5.1-sw + */ + HostEvent["SetActiveTab"] = "SetActiveTab"; + /** + * Updates the runtime filters applied on a Liveboard. The filter + * attributes passed with this event are appended to the existing runtime + * filters applied on a Liveboard. + * + * **Note**: `HostEvent.UpdateRuntimeFilters` is supported in `LiveboardEmbed` + * and `AppEmbed` only. In full application embedding, this event updates + * the runtime filters applied on the Liveboard and saved Answer objects. + * + * @param - Array of {@link RuntimeFilter} objects. Each item includes: + * - `columnName`: Name of the column to filter on. + * - `operator`: {@link RuntimeFilterOp} to apply. For more information, see + * link:https://developers.thoughtspot.com/docs/runtime-filters#rtOperator[Developer Documentation]. + * - `values`: List of operands. Some operators such as EQ and LE allow a single + * value, whereas BW and IN accept multiple values. + * + * **Note**: Updating runtime filters resets the ThoughtSpot + * object to its original state and applies new filter conditions. + * Any user changes (like drilling into a visualization) + * will be cleared, restoring the original visualization + * with the updated filters. + * + + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateRuntimeFilters, [ + * {columnName: "state",operator: RuntimeFilterOp.EQ,values: ["michigan"]}, + * {columnName: "item type",operator: RuntimeFilterOp.EQ,values: ["Jackets"]} + * ]) + * ``` + * @example + * ```js + * // Update runtime filters from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.UpdateRuntimeFilters, [ + * {columnName: "region", operator: RuntimeFilterOp.EQ, values: ["west"]}, + * {columnName: "product", operator: RuntimeFilterOp.IN, values: ["shoes", "boots"]} + * ], ContextType.Liveboard); + * ``` + * @version SDK: 1.9.0 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + * @important + */ + HostEvent["UpdateRuntimeFilters"] = "UpdateRuntimeFilters"; + /** + * Navigate to a specific page in the embedded ThoughtSpot application. + * This is the same as calling `appEmbed.navigateToPage(path, true)`. + * @param - `path` - the path to navigate to go forward or back. The path value can + * be a number; for example, `1`, `-1`. + * @example + * ```js + * appEmbed.navigateToPage(-1) + * ``` + * @version SDK: 1.12.0 | ThoughtSpot: 8.4.0.cl, 8.4.1.sw + */ + HostEvent["Navigate"] = "Navigate"; + /** + * Open the filter panel for a particular column. + * Works with Search and Liveboard embed. + * @param - { columnId: string, + * name: string, + * type: ATTRIBUTE/MEASURE, + * dataType: INT64/CHAR/DATE } + * @example + * ```js + * searchEmbed.trigger(HostEvent.OpenFilter, + * {column: { columnId: '', name: 'column name', type: 'ATTRIBUTE', dataType: 'INT64'}}) + * ``` + * @example + * ```js + * LiveboardEmbed.trigger(HostEvent.OpenFilter, + * { column: {columnId: ''}}) + * ``` + * @example + * ```js + * // Open filter from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.OpenFilter, { + * column: { columnId: '', name: 'region', type: 'ATTRIBUTE', dataType: 'CHAR'} + * }, ContextType.Search); + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl + */ + HostEvent["OpenFilter"] = "openFilter"; + /** + * Add columns to the current search query. + * @param - { columnIds: string[] } + * @example + * ```js + * searchEmbed.trigger(HostEvent.AddColumns, { columnIds: ['',''] }) + * ``` + * @example + * ```js + * // Add columns from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.AddColumns, { + * columnIds: ['col-guid-1', 'col-guid-2'] + * }, ContextType.Search); + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl + */ + HostEvent["AddColumns"] = "addColumns"; + /** + * Remove a column from the current search query. + * @param - { columnId: string } + * @example + * ```js + * searchEmbed.trigger(HostEvent.RemoveColumn, { columnId: '' }) + * ``` + * @example + * ```js + * // Remove column from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.RemoveColumn, { + * columnId: 'column-guid' + * }, ContextType.Search); + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl + */ + HostEvent["RemoveColumn"] = "removeColumn"; + /** + * Get the transient state of a Liveboard as encoded content. + * This includes unsaved and ad hoc changes such as + * Liveboard filters, runtime filters applied on visualizations on a + * Liveboard, and Liveboard layout, changes to visualizations such as + * sorting, toggling of legends, and data drill down. + * For more information, see + * link:https://developers.thoughtspot.com/docs/fetch-data-and-report-apis#transient-lb-content[Liveboard data with unsaved changes]. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.getExportRequestForCurrentPinboard).then( + * data=>console.log(data)) + * ``` + * @version SDK: 1.13.0 | ThoughtSpot: 8.5.0.cl, 8.8.1.sw + */ + HostEvent["getExportRequestForCurrentPinboard"] = "getExportRequestForCurrentPinboard"; + /** + * Trigger **Pin** action on an embedded object. + * If no parameters are defined, the pin action is triggered + * for the Answer that the user is currently on + * and a modal opens for Liveboard selection. + * To add an Answer or visualization to a Liveboard programmatically without + * requiring additional user input via the *Pin to Liveboard* modal, define + * the following parameters: + * + * @param - Includes the following keys: + * - `vizId`: GUID of the saved Answer or Spotter visualization ID to pin to a + * Liveboard. + * Optional when pinning a new chart or table generated from a Search query. + * **Required** in Spotter Embed. + * - `liveboardId`: GUID of the Liveboard to pin an Answer. If there is no Liveboard, + * specify the `newLiveboardName` parameter to create a new Liveboard. + * - `tabId`: GUID of the Liveboard tab. Adds the Answer to the Liveboard tab + * specified in the code. + * - `newVizName`: Name string for the Answer or visualization. If defined, + * this parameter adds a new visualization object or creates a copy of the + * Answer or visualization specified in `vizId`. + * Required. + * - `newLiveboardName`: Name string for the Liveboard. + * Creates a new Liveboard object with the specified name. + * - `newTabName`: Name of the tab. Adds a new tab Liveboard specified + * in the code. + * + * @example + * ```js + * const pinResponse = await appEmbed.trigger(HostEvent.Pin, { + * vizId: "123", + * newVizName: "Sales by region", + * liveboardId: "123", + * tabId: "123" + * }); + * ``` + * @example + * ```js + * const pinResponse = await appEmbed.trigger(HostEvent.Pin, { + * newVizName: "Total sales of Jackets", + * liveboardId: "123" + * }); + * ``` + * + * @example + * ```js + * const pinResponse = await searchEmbed.trigger(HostEvent.Pin, { + * newVizName: "Sales by state", + * newLiveboardName: "Sales", + * newTabName: "Products" + * }); + * ``` + * @example + * ```js + * appEmbed.trigger(HostEvent.Pin) + * ``` + * @example + * ```js + + * // You can use the Data event dispatched on each answer creation to get the vizId and use in Pin host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.Pin, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Using context parameter to specify the context type (SDK: 1.45.2+) + * // Pin from a search answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Pin, { + * vizId: "123", + * newVizName: "Sales by region", + * liveboardId: "456" + * }, ContextType.Search); + * ``` + * @example + * ```js + * // Pin from an answer context (explore modal/page) (SDK: 1.45.2+) + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Pin, { + * vizId: "789", + * newVizName: "Revenue trends", + * liveboardId: "456" + * }, ContextType.Answer); + * ``` + * @example + * ```js + * // Pin from a spotter context (SDK: 1.45.2+) + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Pin, { + * vizId: latestSpotterVizId, + * newVizName: "AI-generated insights", + * liveboardId: "456" + * }, ContextType.Spotter); + * ``` + * + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["Pin"] = "pin"; + /** + * Trigger the **Show Liveboard details** action + * on an embedded Liveboard. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.LiveboardInfo) + *``` + * @example + * ```js + * // Show liveboard info from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.LiveboardInfo, {}, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["LiveboardInfo"] = "pinboardInfo"; + /** + * Trigger the **Schedule** action on an embedded Liveboard. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Schedule) + * ``` + * @example + * ```js + * // Schedule from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Schedule, {}, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["Schedule"] = "subscription"; + /** + * Trigger the **Manage schedule** action on an embedded Liveboard + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ScheduleList) + * ``` + * @example + * ```js + * // Manage schedules from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.ScheduleList, {}, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["SchedulesList"] = "schedule-list"; + /** + * Trigger the **Export TML** action on an embedded Liveboard or + * Answer. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ExportTML) + * ``` + * @example + * ```js + * // Export TML from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.ExportTML, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Export TML from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.ExportTML, {}, ContextType.Search); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["ExportTML"] = "exportTSL"; + /** + * Trigger the **Edit TML** action on an embedded Liveboard or + * saved Answers in the full application embedding. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.EditTML) + * ``` + * @example + * ```js + * // Edit TML from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.EditTML, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Edit TML from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.EditTML, {}, ContextType.Search); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["EditTML"] = "editTSL"; + /** + * Trigger the **Update TML** action on an embedded Liveboard. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateTML) + * ``` + * @example + * ```js + * // Update TML from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.UpdateTML, {}, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["UpdateTML"] = "updateTSL"; + /** + * Trigger the **Download PDF** action on an embedded Liveboard, + * visualization or Answer. + * + * @param - `vizId` refers to the Answer ID in Spotter embed and is required in Spotter embed. + * + * **NOTE**: The **Download** > **PDF** action is available on + * visualizations and Answers if the data is in tabular format. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.DownloadAsPdf) + * ``` + * @example + * ```js + + * // You can use the Data event dispatched on each answer creation to get the vizId and use in DownloadAsPdf host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.DownloadAsPdf, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Download as PDF from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.DownloadAsPdf, {}, ContextType.Search); + * ``` + * @example + * ```js + * // Download as PDF from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.DownloadAsPdf, {}, ContextType.Liveboard); + * ``` + * + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["DownloadAsPdf"] = "downloadAsPdf"; + /** + * Trigger the **Download Liveboard as Continuous PDF** action on an + * embedded Liveboard. + * + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.DownloadLiveboardAsContinuousPDF) + * ``` + * + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + HostEvent["DownloadLiveboardAsContinuousPDF"] = "downloadLiveboardAsContinuousPDF"; + /** + * Trigger the **AI Highlights** action on an embedded Liveboard + * + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.AIHighlights) + * ``` + * @version SDK: 1.44.0 | ThoughtSpot: 10.15.0.cl + */ + HostEvent["AIHighlights"] = "AIHighlights"; + /** + * Trigger the **Make a copy** action on a Liveboard, + * visualization, or Answer page. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.MakeACopy) + * ``` + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.MakeACopy, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * @example + * ```js + * vizEmbed.trigger(HostEvent.MakeACopy) + * ``` + * @example + * ```js + * searchEmbed.trigger(HostEvent.MakeACopy) + * ``` + * @example + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in MakeACopy host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.MakeACopy, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Make a copy from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.MakeACopy, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Make a copy from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.MakeACopy, {}, ContextType.Search); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["MakeACopy"] = "makeACopy"; + /** + * Trigger the **Delete** action for a Liveboard. + * @example + * ```js + * appEmbed.trigger(HostEvent.Remove) + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Remove) + * ``` + * @version SDK: 1.37.0 | ThoughtSpot: 10.8.0.cl, 10.10.0.sw + */ + HostEvent["Remove"] = "delete"; + /** + * Trigger the **Explore** action on a visualization. + * @param - an object with `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Explore, {vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * @example + * ```js + * // Explore from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Explore, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["Explore"] = "explore"; + /** + * Trigger the **Create alert** action on a KPI chart + * in a Liveboard or saved Answer. + * @param - an object with `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.CreateMonitor, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }) + * ``` + * @example + * ```js + * searchEmbed.trigger(HostEvent.CreateMonitor) + * ``` + * @example + * ```js + * // Create monitor from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.CreateMonitor, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Create monitor from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.CreateMonitor, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["CreateMonitor"] = "createMonitor"; + /** + * Trigger the **Manage alerts** action on a KPI chart + * in a visualization or saved Answer. + * @param - an object with `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ManageMonitor, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }) + * ``` + * @example + * ```js + * searchEmbed.trigger(HostEvent.ManageMonitor) + * ``` + * @example + * ```js + * vizEmbed.trigger(HostEvent.ManageMonitor) + * ``` + * @example + * ```js + * // Manage monitor from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.ManageMonitor, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Manage monitor from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.ManageMonitor, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["ManageMonitor"] = "manageMonitor"; + /** + * Trigger the **Edit** action on a Liveboard or a visualization + * on a Liveboard. + * + * This event is not supported in visualization embed and search embed. + * @param - Object parameter. Includes the following keys: + * - `vizId`: To trigger the action for a specific visualization in Liveboard embed, + * pass in `vizId` as a key. In Spotter embed, `vizId` refers to the Answer ID and + * is **required**. + * + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Edit) + * ``` + * ```js + * liveboardEmbed.trigger(HostEvent.Edit, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * @example + * ```js + * spotterEmbed.trigger(HostEvent.Edit); + * ``` + * @example + * ```js + * // Using context parameter to edit liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Edit, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Edit from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Edit, {}, ContextType.Search); + * ``` + * * @example + * ```js + * // Edit from spotter context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Edit, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Spotter); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["Edit"] = "edit"; + /** + * Trigger the **Copy link** action on a Liveboard or visualization + * @param - object - to trigger the action for a + * specific visualization in Liveboard embed, pass in `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.CopyLink) + * ``` + * ```js + * liveboardEmbed.trigger(HostEvent.CopyLink, {vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.CopyLink) + * ``` + * @example + * ```js + * // Copy link from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.CopyLink, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Copy link from liveboard visualization context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.CopyLink, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Copy link from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.CopyLink, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Copy link from liveboard visualization context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.CopyLink, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["CopyLink"] = "embedDocument"; + /** + * Trigger the **Present** action on a Liveboard or visualization + * @param - object - to trigger the action for a specific visualization + * in Liveboard embed, pass in `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Present) + * ``` + * ```js + * liveboardEmbed.trigger(HostEvent.Present, {vizId: '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.Present) + * ``` + * @example + * ```js + * // Present from liveboard visualization context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Present, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Present from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Present, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Present from liveboard visualization context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Present, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Present from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Present, {}, ContextType.Liveboard); + * ``` + * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1.sw + */ + HostEvent["Present"] = "present"; + /** + * Get TML for the current search. + * @example + * ```js + * searchEmbed.trigger(HostEvent.GetTML).then((tml) => { + * console.log( + * tml.answer.search_query // TML representation of the search query + * ); + * }) + * ``` + * @example + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in GetTML host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.GetTML, { + * vizId: latestSpotterVizId + * }).then((tml) => { + * console.log( + * tml.answer.search_query // TML representation of the search query + * ); + * }) + * ``` + * @example + * ```js + * // Get TML from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.GetTML, {}, ContextType.Search).then((tml) => { + * console.log(tml.answer.search_query); + * }); + * ``` + * @example + * ```js + * // Get TML from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.GetTML, {}, ContextType.Search).then((tml) => { + * console.log(tml.answer); + * }); + * ``` + * @version SDK: 1.18.0 | ThoughtSpot: 8.10.0.cl, 9.0.1.sw + * @important + */ + HostEvent["GetTML"] = "getTML"; + /** + * Trigger the **Show underlying data** action on a + * chart or table. + * + * @param - an object with vizId as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ShowUnderlyingData, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.ShowUnderlyingData) + * ``` + * ```js + * searchEmbed.trigger(HostEvent.ShowUnderlyingData) + * ``` + * @example + * ```js + * // Show underlying data from liveboard visualization context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.ShowUnderlyingData, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Show underlying data from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.ShowUnderlyingData, {}, ContextType.Search); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["ShowUnderlyingData"] = "showUnderlyingData"; + /** + * Trigger the **Delete** action for a visualization + * in an embedded Liveboard, or a chart or table + * generated from Search. + * @param - Liveboard embed takes an object with `vizId` as a key. + * Can be left empty if embedding Search or visualization. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Delete, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * searchEmbed.trigger(HostEvent.Delete) + * ``` + * @example + * ```js + * // Delete from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Delete, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Delete from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Delete, {}, ContextType.Search); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["Delete"] = "onDeleteAnswer"; + /** + * Trigger the **SpotIQ analyze** action on a + * chart or table. + * @param - Liveboard embed takes `vizId` as a + * key. Can be left undefined when embedding Search or + * visualization. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SpotIQAnalyze, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.SpotIQAnalyze) + * ``` + * ```js + * searchEmbed.trigger(HostEvent.SpotIQAnalyze) + * ``` + * @example + * ```js + * // SpotIQ analyze from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.SpotIQAnalyze, { vizId: '730496d6-6903-4601-937e-2c691821af3c' }, ContextType.Search); + * ``` + * @example + * ```js + * // SpotIQ analyze from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SpotIQAnalyze, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["SpotIQAnalyze"] = "spotIQAnalyze"; + /** + * Trigger the **Download** action on charts in + * the embedded view. + * Use {@link HostEvent.DownloadAsPng} instead. + * + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + * + * @deprecated from SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl ,9.4.1.sw + * @param - `vizId` refers to the Visualization ID in Spotter embed and is required in Spotter embed. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Download, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * embed.trigger(HostEvent.Download) + * ``` + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in Download host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.Download, { vizId: latestSpotterVizId }); + * ``` + */ + HostEvent["Download"] = "downloadAsPng"; + /** + * Trigger the **Download** > **PNG** action on + * charts in the embedded view. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.DownloadAsPng, + * {vizId:'730496d6-6903-4601-937e-2c691821af3c'}) + * + * vizEmbed.trigger(HostEvent.DownloadAsPng) + * + * searchEmbed.trigger(HostEvent.DownloadAsPng) + * + * // You can use the Data event dispatched on each answer creation to get the vizId and use in DownloadAsPng host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.DownloadAsPng, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Download as PNG from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.DownloadAsPng, {}, ContextType.Search); + * ``` + * + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.4.1.sw + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + HostEvent["DownloadAsPng"] = "downloadAsPng"; + /** + * Trigger the **Download** > **CSV** action on tables in + * the embedded view. + * @param - `vizId` refers to the Visualization ID in Spotter embed and is required in Spotter embed. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.DownloadAsCsv, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.DownloadAsCsv) + * ``` + * ```js + * searchEmbed.trigger(HostEvent.DownloadAsCsv) + * ``` + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in DownloadAsCsv host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.DownloadAsCsv, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Download as CSV from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.DownloadAsCsv, {}, ContextType.Search); + * ``` + * @example + * ```js + * // Download as CSV from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.DownloadAsCsv, {}, ContextType.Search); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["DownloadAsCsv"] = "downloadAsCSV"; + /** + * Trigger the **Download** > **XLSX** action on tables + * in the embedded view. + * @param - `vizId` refers to the Visualization ID in Spotter embed and is required in Spotter embed. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.DownloadAsXlsx, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.DownloadAsXlsx) + * ``` + * ```js + * searchEmbed.trigger(HostEvent.DownloadAsXlsx) + * ``` + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in DownloadAsXlsx host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.DownloadAsXlsx, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Download as XLSX from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.DownloadAsXlsx, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Download as XLSX from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.DownloadAsXlsx, {}, ContextType.Search); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["DownloadAsXlsx"] = "downloadAsXLSX"; + /** + * Trigger the **Share** action on an embedded + * Liveboard or Answer. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.Share) + * ``` + * ```js + * searchEmbed.trigger(HostEvent.Share) + * ``` + * @example + * ```js + * // Share from Liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.Share, {}, ContextType.Liveboard); + * ``` + * @example + * ```js + * // Share from search-answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Share, {}, ContextType.Search); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["Share"] = "share"; + /** + * Trigger the **Save** action on a Liveboard, Answer, or Spotter. + * Saves the changes. + * + * @param - `vizId` refers to the Spotter Visualization Id used in Spotter embed. + * It is required and can be retrieved from the data embed event. + * + * @example + * ```js + * // Save changes in a Liveboard + * liveboardEmbed.trigger(HostEvent.Save) + * ``` + * + * ```js + * // Save the current Answer in Search embed + * searchEmbed.trigger(HostEvent.Save) + * ``` + * + * ```js + * // Save a Visualization in Spotter (requires vizId) + * spotterEmbed.trigger(HostEvent.Save, { + * vizId: "730496d6-6903-4601-937e-2c691821af3c" + * }) + * ``` + * + * ```js + * // How to get the vizId in Spotter? + * + * // You can use the Data event dispatched on each answer creation to get the vizId. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.Save, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Save from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.Save, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Save from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.Save, {}, ContextType.Search); + * ``` + * + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["Save"] = "save"; + /** + * Trigger the **Sync to Sheets** action on an embedded visualization or Answer + * Sends data from an Answer or Liveboard visualization to a Google sheet. + * @param - an object with `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SyncToSheets, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.SyncToSheets) + * ``` + * @example + * ```js + * // Sync to sheets from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.SyncToSheets, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Sync to sheets from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SyncToSheets, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["SyncToSheets"] = "sync-to-sheets"; + /** + * Trigger the **Sync to Other Apps** action on an embedded visualization or Answer + * Sends data from an Answer or Liveboard visualization to third-party apps such + * as Slack, Salesforce, Microsoft Teams, ServiceNow and so on. + * @param - an object with vizId as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SyncToOtherApps, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.SyncToOtherApps) + * ``` + * @example + * ```js + * // Sync to other apps from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.SyncToOtherApps, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Sync to other apps from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SyncToOtherApps, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["SyncToOtherApps"] = "sync-to-other-apps"; + /** + * Trigger the **Manage pipelines** action on an embedded + * visualization or Answer. + * Allows users to manage ThoughtSpot Sync pipelines. + * @param - an object with `vizId` as a key + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ManagePipelines, {vizId: + * '730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * ```js + * vizEmbed.trigger(HostEvent.ManagePipelines) + * ``` + * @example + * ```js + * // Manage pipelines from answer context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * appEmbed.trigger(HostEvent.ManagePipelines, {}, ContextType.Answer); + * ``` + * @example + * ```js + * // Manage pipelines from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.ManagePipelines, { + * vizId: '730496d6-6903-4601-937e-2c691821af3c' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw + */ + HostEvent["ManagePipelines"] = "manage-pipeline"; + /** + * Reset search operation on the Search or Answer page. + * @example + * ```js + * searchEmbed.trigger(HostEvent.ResetSearch) + * ``` + * ```js + * appEmbed.trigger(HostEvent.ResetSearch) + * ``` + * @example + * ```js + * // Reset search from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * searchEmbed.trigger(HostEvent.ResetSearch, {}, ContextType.Search); + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.0.1.sw + */ + HostEvent["ResetSearch"] = "resetSearch"; + /** + * Get details of filters applied on the Liveboard. + * Returns arrays containing Liveboard filter and runtime filter elements. + * @example + * ```js + * const data = await liveboardEmbed.trigger(HostEvent.GetFilters); + * console.log('data', data); + * ``` + * @example + * ```js + * // Get filters from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * const data = await liveboardEmbed.trigger(HostEvent.GetFilters, {}, ContextType.Liveboard); + * console.log('filters', data); + * ``` + * @version SDK: 1.23.0 | ThoughtSpot: 9.4.0.cl + */ + HostEvent["GetFilters"] = "getFilters"; + /** + * Update one or several filters applied on a Liveboard. + * @param - Includes the following keys: + * - `filter`: A single filter object containing column name, filter operator, and + * values. + * - `filters`: Multiple filter objects with column name, filter operator, + * and values for each. + * + * Each filter object must include the following attributes: + * + * `column` - Name of the column to filter on. + * + * `oper` - Filter operator, for example, EQ, IN, CONTAINS. + * For information about the supported filter operators, + * see link:https://developers.thoughtspot.com/docs/runtime-filters#rtOperator[Developer Documentation]. + * + * `values` - An array of one or several values. The value definition on the + * data type you choose to filter on. For a complete list of supported data types, + * see + * link:https://developers.thoughtspot.com/docs/runtime-filters#_supported_data_types[Supported + * data types]. + * + * `type` - To update filters for date time, specify the date format type. + * For more information and examples, see link:https://developers.thoughtspot.com/docs/embed-liveboard#_date_filters[Date filters]. + * @example + * ```js + * + * liveboardEmbed.trigger(HostEvent.UpdateFilters, { + * filter: { + * column: "item type", + * oper: "IN", + * values: ["bags","shirts"] + * } + * }); + * ``` + * @example + * ```js + * + * liveboardEmbed.trigger(HostEvent.UpdateFilters, { + * filter: { + * column: "date", + * oper: "EQ", + * values: ["JULY","2023"], + * type: "MONTH_YEAR" + * } + * }); + * ``` + * @example + * + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateFilters, { + * filters: [{ + * column: "Item Type", + * oper: 'IN', + * values: ["bags","shirts"] + * }, + * { + * column: "Region", + * oper: 'IN', + * values: ["West","Midwest"] + * }, + * { + * column: "Date", + * oper: 'EQ', + * values: ["2023-07-31"], + * type: "EXACT_DATE" + * }] + * }); + * ``` + * If there are multiple columns with the same name, consider + * using `WORKSHEET_NAME::COLUMN_NAME` format. + * + * @example + * + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateFilters, { + * filters: [{ + * column: "(Sample) Retail - Apparel::city", + * oper: 'IN', + * values: ["atlanta"] + * }, + * { + * column: "(Sample) Retail - Apparel::Region", + * oper: 'IN', + * values: ["West","Midwest"] + * }] + * }); + * ``` + * @example + * ```js + * // Update filters from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.UpdateFilters, { + * filter: { + * column: "item type", + * oper: "IN", + * values: ["shoes", "boots"] + * } + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.23.0 | ThoughtSpot: 9.4.0.cl + */ + HostEvent["UpdateFilters"] = "updateFilters"; + /** + * Get tab details for the current Liveboard. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.GetTabs).then((tabDetails) => { + * console.log( + * tabDetails // TabDetails of current Liveboard + * ); + * }) + * ``` + * @example + * ```js + * // Get tabs from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.GetTabs, {}, ContextType.Liveboard).then((tabDetails) => { + * console.log('tabs', tabDetails); + * }); + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl + */ + HostEvent["GetTabs"] = "getTabs"; + /** + * Set the visible tabs on a Liveboard. + * @param - an array of ids of tabs to show, the IDs not passed + * will be hidden. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SetVisibleTabs, [ + * '430496d6-6903-4601-937e-2c691821af3c', + * 'f547ec54-2a37-4516-a222-2b06719af726']) + * ``` + * @example + * ```js + * // Set visible tabs from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SetVisibleTabs, [ + * '430496d6-6903-4601-937e-2c691821af3c', + * 'f547ec54-2a37-4516-a222-2b06719af726' + * ], ContextType.Liveboard); + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + */ + HostEvent["SetVisibleTabs"] = "SetPinboardVisibleTabs"; + /** + * Set the hidden tabs on a Liveboard. + * @param - an array of the IDs of the tabs to hide. + * The IDs not passed will be shown. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SetHiddenTabs, [ + * '630496d6-6903-4601-937e-2c691821af3c', + * 'i547ec54-2a37-4516-a222-2b06719af726']) + * ``` + * @example + * ```js + * // Set hidden tabs from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.SetHiddenTabs, [ + * '630496d6-6903-4601-937e-2c691821af3c', + * 'i547ec54-2a37-4516-a222-2b06719af726' + * ], ContextType.Liveboard); + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + */ + HostEvent["SetHiddenTabs"] = "SetPinboardHiddenTabs"; + /** + * Get the Answer session for a Search or + * Liveboard visualization. + * + * Note: This event is not typically used directly. Instead, use the + * `getAnswerService()` method on the embed instance to get an AnswerService + * object that provides a more convenient interface for working with answers. + * + * @example + * ```js + * // Preferred way to get an AnswerService + * const service = await embed.getAnswerService(); + * + * // Alternative direct usage (not recommended) + * const {session} = await embed.trigger( + * HostEvent.GetAnswerSession, { + * vizId: '123', // For Liveboard Visualization. + * }) + * ``` + * @example + * ```js + * // Preferred way to get an AnswerService + * const service = await embed.getAnswerService(); + * + * // Alternative direct usage (not recommended) + * const {session} = await embed.trigger( HostEvent.GetAnswerSession ) + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.10.0.cl, 10.1.0.sw + */ + HostEvent["GetAnswerSession"] = "getAnswerSession"; + /** + * Trigger the *Ask Sage* action for visualizations + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.AskSage, + * {vizId:'730496d6-6903-4601-937e-2c691821af3c'}) + * ``` + * @version SDK: 1.29.0 | ThoughtSpot Cloud: 9.12.0.cl + */ + HostEvent["AskSage"] = "AskSage"; + /** + * Trigger cross filter update action on a Liveboard. + * + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateCrossFilter, { + * vizId: 'b535c760-8bbe-4e6f-bb26-af56b4129a1e', + * conditions: [ + * { columnName: 'Category', values: ['mfgr#12','mfgr#14'] }, + * { columnName: 'color', values: ['mint','hot'] }, + * ], + * }); + * ``` + * @version SDK: 1.29.0 | ThoughtSpot Cloud: 10.0.0.cl, 10.1.0.sw + */ + HostEvent["UpdateCrossFilter"] = "UpdateCrossFilter"; + /** + * Trigger reset action for a personalized Liveboard view. + * This event is deprecated. Use {@link HostEvent.ResetLiveboardPersonalizedView} instead. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ResetLiveboardPersonalisedView); + * ``` + * @version SDK: 1.29.0 | ThoughtSpot Cloud: 10.1.0.cl, 10.1.0.sw + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + HostEvent["ResetLiveboardPersonalisedView"] = "ResetLiveboardPersonalisedView"; + /** + * Trigger reset action for a personalized Liveboard view. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.ResetLiveboardPersonalizedView); + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + HostEvent["ResetLiveboardPersonalizedView"] = "ResetLiveboardPersonalisedView"; + /** + * Triggers an action to update Parameter values on embedded + * Answers, Liveboard, and Spotter answer in Edit mode. + * @param - Includes the following keys for each item: + * - `name`: Name of the parameter. + * - `value`: The value to set for the parameter. + * - `isVisibleToUser`: Optional. To control the visibility of the parameter chip. + * + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateParameters, [{ + * name: "Integer Range Param", + * value: 10, + * isVisibleToUser: false + * }]) + * ``` + * @example + * ```js + * // Update parameters from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.UpdateParameters, [{ + * name: "Region Param", + * value: "West", + * isVisibleToUser: true + * }], ContextType.Liveboard); + * ``` + * @version SDK: 1.29.0 | ThoughtSpot: 10.1.0.cl, 10.1.0.sw + */ + HostEvent["UpdateParameters"] = "UpdateParameters"; + /** + * Triggers GetParameters to fetch the runtime Parameters. + * @param - `vizId` refers to the Answer ID in Spotter embed and is required in Spotter embed. + * ```js + * liveboardEmbed.trigger(HostEvent.GetParameters).then((parameter) => { + * console.log('parameters', parameter); + * }); + * ``` + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in GetParameters host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.GetParameters, { vizId: latestSpotterVizId }); + *``` + * @example + * ```js + * // Get parameters from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.GetParameters, {}, + * ContextType.Liveboard).then((parameters) => { + * console.log('parameters', parameters); + * }); + * ``` + * @version SDK: 1.29.0 | ThoughtSpot: 10.1.0.cl, 10.1.0.sw + */ + HostEvent["GetParameters"] = "GetParameters"; + /** + * Triggers an event to update a personalized view of a Liveboard. + * This event is deprecated. Use {@link HostEvent.UpdatePersonalizedView} instead. + * ```js + * liveboardEmbed.trigger(HostEvent.UpdatePersonalisedView, {viewId: '1234'}) + * ``` + * @example + * ```js + * // Update personalized view from liveboard context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * liveboardEmbed.trigger(HostEvent.UpdatePersonalisedView, { + * viewId: '1234' + * }, ContextType.Liveboard); + * ``` + * @version SDK: 1.36.0 | ThoughtSpot: 10.6.0.cl + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + HostEvent["UpdatePersonalisedView"] = "UpdatePersonalisedView"; + /** + * Triggers an event to update a personalized view of a Liveboard. + * ```js + * liveboardEmbed.trigger(HostEvent.UpdatePersonalisedView, {viewId: '1234'}) + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + HostEvent["UpdatePersonalizedView"] = "UpdatePersonalisedView"; + /** + * Triggers selection of a specific Personalized View on a + * Liveboard without reloading the embed. Pass either a + * `viewId` (GUID) or `viewName`. If both are provided, `viewId` takes precedence. + * If neither is provided, the Liveboard resets to the original/default view. + * When a `viewName` is provided and multiple views share + * the same name, the first match is selected. + * @example + * ```js + * liveboardEmbed.trigger( + * HostEvent.SelectPersonalizedView, + * { viewId: '2a021a12-1aed-425d-984b-141ee916ce72' }, + * ) + * ``` + * @example + * ```js + * // Select by name + * liveboardEmbed.trigger( + * HostEvent.SelectPersonalizedView, + * { viewName: 'Dr Smith Cardiology' }, + * ) + * ``` + * @example + * ```js + * // Reset to default view + * liveboardEmbed.trigger( + * HostEvent.SelectPersonalizedView, + * {}, + * ) + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + HostEvent["SelectPersonalizedView"] = "SelectPersonalisedView"; + /** + * @hidden + * Notify when info call is completed successfully + * ```js + * liveboardEmbed.trigger(HostEvent.InfoSuccess, data); + * ``` + * @version SDK: 1.36.0 | ThoughtSpot: 10.6.0.cl + */ + HostEvent["InfoSuccess"] = "InfoSuccess"; + /** + * Trigger the save action for an Answer. + * To programmatically save an answer without opening the + * *Describe your Answer* modal, define the `name` and `description` + * properties. + * If no parameters are specified, the save action is + * triggered with a modal to prompt users to + * add a name and description for the Answer. + * @param - Includes the following keys: + * - `vizId`: Refers to the Answer ID in Spotter embed and is **required** in Spotter + * embed. + * - `name`: Optional. Name string for the Answer. + * - `description`: Optional. Description text for the Answer. + * @example + * ```js + * const saveAnswerResponse = await searchEmbed.trigger(HostEvent.SaveAnswer, { + * name: "Sales by states", + * description: "Total sales by states in MidWest" + * }); + * ``` + * @example + * ```js + * // You can use the Data event dispatched on each answer creation to get the vizId and use in SaveAnswer host event. + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.SaveAnswer, { vizId: latestSpotterVizId }); + * ``` + * @example + * ```js + * // Using context parameter to save answer from search context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * const saveAnswerResponse = await appEmbed.trigger(HostEvent.SaveAnswer, { + * name: "Regional sales analysis", + * description: "Sales breakdown by region" + * }, ContextType.Search); + * ``` + * @example + * ```js + * // Save answer from answer context (explore modal) + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * const saveAnswerResponse = await appEmbed.trigger(HostEvent.SaveAnswer, { + * name: "Modified analysis", + * description: "Updated from explore view" + * }, ContextType.Answer); + * ``` + * @example + * ```js + * // Save answer from spotter context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * const saveAnswerResponse = await appEmbed.trigger(HostEvent.SaveAnswer, { + * vizId: latestSpotterVizId, + * name: "AI insights", + * description: "Generated from Spotter" + * }, ContextType.Spotter); + * ``` + * @version SDK: 1.36.0 | ThoughtSpot: 10.6.0.cl + */ + HostEvent["SaveAnswer"] = "saveAnswer"; + /** + * EmbedApi + * @hidden + */ + HostEvent["UIPassthrough"] = "UiPassthrough"; + /** + * Triggers the table visualization re-render with the updated data. + * Includes the following properties: + * @param - `columnDataLite` - an array of object containing the + * data value modifications retrieved from the `EmbedEvent.TableVizRendered` + * payload.For example, { columnDataLite: []}`. + * + * @example + * ```js + * searchEmbed.on(EmbedEvent.TableVizRendered, (payload) => { + * console.log(payload); + * const columnDataLite = payload.data.data.columnDataLite; + * columnDataLite[0].dataValue[0]="new fob"; + * console.log('>>> new Data', columnDataLite); + * searchEmbed.trigger(HostEvent.TransformTableVizData, columnDataLite); + * }) + * ``` + * @version SDK: 1.37.0 | ThoughtSpot: 10.8.0.cl + */ + HostEvent["TransformTableVizData"] = "TransformTableVizData"; + /** + * Triggers a search operation with the search tokens specified in + * the search query string in spotter embed. + * @param - Includes the following keys: + * - `query`: Text string in Natural Language format. + * - `executeSearch`: Boolean to execute search and update search query. + * @example + * ```js + * spotterEmbed.trigger(HostEvent.SpotterSearch, { + * query: 'revenue per year', + * executeSearch: true, + * }) + * ``` + * @example + * ```js + * // Spotter search from spotter context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * spotterEmbed.trigger(HostEvent.SpotterSearch, { + * query: 'sales by region', + * executeSearch: true + * }, ContextType.Spotter); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HostEvent["SpotterSearch"] = "SpotterSearch"; + /** + * Edits the last prompt in spotter embed. + * @param - `query`: Text string + * @example + * ```js + * spotterEmbed.trigger(HostEvent.EditLastPrompt, "revenue per year"); + * ``` + * @example + * ```js + * // Edit last prompt from spotter context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * spotterEmbed.trigger(HostEvent.EditLastPrompt, "sales by region", ContextType.Spotter); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HostEvent["EditLastPrompt"] = "EditLastPrompt"; + /** + * Opens the data source preview modal in Spotter Embed. + * @example + * ```js + * spotterEmbed.trigger(HostEvent.PreviewSpotterData); + * ``` + * @example + * ```js + * // Preview spotter data from spotter context + * import { ContextType } from '@thoughtspot/visual-embed-sdk'; + * spotterEmbed.trigger(HostEvent.PreviewSpotterData, {}, ContextType.Spotter); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HostEvent["PreviewSpotterData"] = "PreviewSpotterData"; + /** + * Opens the Add to Coaching modal from a visualization in Spotter Embed. + * @param - `vizId ` refers to the Visualization ID in Spotter embed and is required. + * @example + * ```js + * spotterEmbed.trigger(HostEvent.AddToCoaching, { vizId: '730496d6-6903-4601-937e-2c691821af3c' }); + * + * ``` + * @version SDK: 1.45.0 | ThoughtSpot: 26.2.0.cl + */ + HostEvent["AddToCoaching"] = "addToCoaching"; + /** + * Opens the data model instructions modal in Spotter Embed. + * @example + * ```js + * spotterEmbed.trigger(HostEvent.DataModelInstructions); + * ``` + * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl + */ + HostEvent["DataModelInstructions"] = "DataModelInstructions"; + /** + * Resets the Spotter Embed Conversation. + * @example + * ```js + * spotterEmbed.trigger(HostEvent.ResetSpotterConversation); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HostEvent["ResetSpotterConversation"] = "ResetSpotterConversation"; + /** + * Deletes the last prompt in spotter embed. + * @example + * ```js + * spotterEmbed.trigger(HostEvent.DeleteLastPrompt); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HostEvent["DeleteLastPrompt"] = "DeleteLastPrompt"; + /** + * Toggle the visualization to chart or table view. + * @param - `vizId ` refers to the Visualization ID in Spotter embed and is required. + * @example + * ```js + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.AnswerChartSwitcher, { vizId: latestSpotterVizId }); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HostEvent["AnswerChartSwitcher"] = "answerChartSwitcher"; + /** + * @hidden + * Trigger exit from presentation mode when user exits fullscreen. + * This is automatically triggered by the SDK when fullscreen mode is exited. + * ```js + * liveboardEmbed.trigger(HostEvent.ExitPresentMode); + * ``` + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ + HostEvent["ExitPresentMode"] = "exitPresentMode"; + /** + * Triggers the full height lazy load data. + * @example + * ```js + * liveboardEmbed.on(EmbedEvent.RequestVisibleEmbedCoordinates, (payload) => { + * console.log(payload); + * }); + * ``` + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + * + * @hidden + */ + HostEvent["VisibleEmbedCoordinates"] = "visibleEmbedCoordinates"; + /** + * Trigger the *Spotter* action for visualizations present on the liveboard's vizzes. + * @param - `vizId` refers to the Visualization ID in Spotter embed and is required. + * @example + * ```js + * let latestSpotterVizId = ''; + * spotterEmbed.on(EmbedEvent.Data, (payload) => { + * latestSpotterVizId = payload.data.id; + * }); + * + * spotterEmbed.trigger(HostEvent.AskSpotter, { vizId: latestSpotterVizId }); + * ``` + * @version SDK: 1.41.0 | ThoughtSpot: 10.12.0.cl + */ + HostEvent["AskSpotter"] = "AskSpotter"; + /** + * @hidden + * Triggers the update of the embed params. + * + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.UpdateEmbedParams, viewConfig); + * ``` + */ + HostEvent["UpdateEmbedParams"] = "updateEmbedParams"; + /** + * Triggered when the embed needs to be destroyed. This is used to clean up any embed-related resources internally. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.DestroyEmbed); + * ``` + * @version SDK: 1.41.0 | ThoughtSpot: 10.12.0.cl + */ + HostEvent["DestroyEmbed"] = "EmbedDestroyed"; + /** + * Triggers a new conversation in Spotter embed. + * + * This feature is available only when chat history is enabled on your ThoughtSpot + * instance. Contact your admin or ThoughtSpot Support to enable chat history on your + * instance. + * + * @example + * ```js + * spotterEmbed.trigger(HostEvent.StartNewSpotterConversation); + * ``` + * @version SDK: 1.45.0 | ThoughtSpot: 26.2.0.cl + */ + HostEvent["StartNewSpotterConversation"] = "StartNewSpotterConversation"; + /** + * @hidden + * Get the current context of the embedded page. + * + * @example + * ```js + * const context = await liveboardEmbed.trigger(HostEvent.GetPageContext); + * ``` + * @version SDK: 1.45.0 | ThoughtSpot: 26.2.0.cl + */ + HostEvent["GetPageContext"] = "GetPageContext"; + /** + * Trigger the **Send Test Email** action in the Liveboard schedule modal. + * Sends a test schedule email to self or all recipients. + * Requires `isSendNowLiveboardSchedulingEnabled` to be enabled. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SendTestScheduleEmail, { + * sendToSelf: true, + * }) + * ``` + * @example + * ```js + * // Send to all recipients + * liveboardEmbed.trigger(HostEvent.SendTestScheduleEmail, { + * sendToSelf: false, + * }) + * ``` + * @version SDK: 1.48.0 | ThoughtSpot Cloud: 26.5.0.cl + */ + HostEvent["SendTestScheduleEmail"] = "sendTestScheduleEmail"; + /** + * Sends a user message (prompt) to the SpotterViz panel programmatically. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @param query - the prompt text to send. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.SpotterVizSendUserMessage, { + * query: 'Show me revenue by region', + * }); + * ``` + */ + HostEvent["SpotterVizSendUserMessage"] = "SpotterVizSendUserMessage"; + /** + * Initializes a new SpotterViz conversation. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.InitSpotterVizConversation); + * ``` + */ + HostEvent["InitSpotterVizConversation"] = "InitSpotterVizConversation"; + /** + * Clears browser cache and fetches new data for liveboard ChartViz Containers. + * Requires `enableLiveboardDataCache` to be enabled. + * @example + * ```js + * liveboardEmbed.trigger(HostEvent.RefreshLiveboardBrowserCache); + * ``` + * @version SDK: 1.49.0 | ThoughtSpot Cloud: 26.6.0.cl + */ + HostEvent["RefreshLiveboardBrowserCache"] = "refreshLiveboardBrowserCache"; +})(HostEvent || (HostEvent = {})); +/** + * The different visual modes that the data sources panel within + * search could appear in, such as hidden, collapsed, or expanded. + */ +var DataSourceVisualMode; +(function (DataSourceVisualMode) { + /** + * The data source panel is hidden. + */ + DataSourceVisualMode["Hidden"] = "hide"; + /** + * The data source panel is collapsed, but the user can manually expand it. + */ + DataSourceVisualMode["Collapsed"] = "collapse"; + /** + * The data source panel is expanded, but the user can manually collapse it. + */ + DataSourceVisualMode["Expanded"] = "expand"; +})(DataSourceVisualMode || (DataSourceVisualMode = {})); +/** + * The query params passed down to the embedded ThoughtSpot app + * containing configuration and/or visual information. + */ +var Param; +(function (Param) { + Param["Tsmcp"] = "tsmcp"; + Param["EmbedApp"] = "embedApp"; + Param["DataSources"] = "dataSources"; + Param["DataSourceMode"] = "dataSourceMode"; + Param["DisableActions"] = "disableAction"; + Param["DisableActionReason"] = "disableHint"; + Param["ForceTable"] = "forceTable"; + Param["preventLiveboardFilterRemoval"] = "preventPinboardFilterRemoval"; + Param["SearchQuery"] = "searchQuery"; + Param["HideActions"] = "hideAction"; + Param["HideObjects"] = "hideObjects"; + Param["HostAppUrl"] = "hostAppUrl"; + Param["EnableVizTransformations"] = "enableVizTransform"; + Param["EnableSearchAssist"] = "enableSearchAssist"; + Param["EnableConnectionNewExperience"] = "newConnectionsExperience"; + Param["EnablePendoHelp"] = "enablePendoHelp"; + Param["HideResult"] = "hideResult"; + Param["UseLastSelectedDataSource"] = "useLastSelectedSources"; + Param["Tag"] = "tag"; + Param["HideTagFilterChips"] = "hideTagFilterChips"; + Param["AutoLogin"] = "autoLogin"; + Param["searchTokenString"] = "searchTokenString"; + Param["executeSearch"] = "executeSearch"; + Param["fullHeight"] = "isFullHeightPinboard"; + Param["livedBoardEmbed"] = "isLiveboardEmbed"; + Param["searchEmbed"] = "isSearchEmbed"; + Param["vizEmbed"] = "isVizEmbed"; + Param["StringIDsUrl"] = "overrideStringIDsUrl"; + Param["Version"] = "sdkVersion"; + Param["ViewPortHeight"] = "viewPortHeight"; + Param["ViewPortWidth"] = "viewPortWidth"; + Param["VisibleActions"] = "visibleAction"; + Param["DisableLoginRedirect"] = "disableLoginRedirect"; + Param["visibleVizs"] = "pinboardVisibleVizs"; + Param["LiveboardV2Enabled"] = "isPinboardV2Enabled"; + Param["DataPanelV2Enabled"] = "enableDataPanelV2"; + Param["ShowAlerts"] = "showAlerts"; + Param["Locale"] = "locale"; + Param["CustomStyle"] = "customStyle"; + Param["ForceSAMLAutoRedirect"] = "forceSAMLAutoRedirect"; + // eslint-disable-next-line @typescript-eslint/no-shadow + Param["AuthType"] = "authType"; + Param["IconSpriteUrl"] = "iconSprite"; + Param["cookieless"] = "cookieless"; + // Deprecated: `isContextMenuEnabledOnLeftClick` + // Introduced: `contextMenuEnabledOnWhichClick` with values: 'left', + // 'right', or 'both'. This update only affects ThoughtSpot URL parameters + // and does not impact existing workflows or use cases. Added support for + // 'both' clicks in `contextMenuTrigger` configuration. + Param["ContextMenuTrigger"] = "contextMenuEnabledOnWhichClick"; + Param["LinkOverride"] = "linkOverride"; + Param["EnableLinkOverridesV2"] = "enableLinkOverridesV2"; + Param["blockNonEmbedFullAppAccess"] = "blockNonEmbedFullAppAccess"; + Param["ShowInsertToSlide"] = "insertInToSlide"; + Param["PrimaryNavHidden"] = "primaryNavHidden"; + Param["HideProfleAndHelp"] = "profileAndHelpInNavBarHidden"; + Param["NavigationVersion"] = "navigationVersion"; + Param["HideHamburger"] = "hideHamburger"; + Param["HideObjectSearch"] = "hideObjectSearch"; + Param["HideNotification"] = "hideNotification"; + Param["HideApplicationSwitcher"] = "applicationSwitcherHidden"; + Param["HideOrgSwitcher"] = "orgSwitcherHidden"; + Param["HideWorksheetSelector"] = "hideWorksheetSelector"; + Param["DisableWorksheetChange"] = "disableWorksheetChange"; + Param["HideSourceSelection"] = "hideSourceSelection"; + Param["DisableSourceSelection"] = "disableSourceSelection"; + Param["HideEurekaResults"] = "hideEurekaResults"; + Param["HideEurekaSuggestions"] = "hideEurekaSuggestions"; + Param["HideAutocompleteSuggestions"] = "hideAutocompleteSuggestions"; + Param["HideLiveboardHeader"] = "hideLiveboardHeader"; + Param["ShowLiveboardDescription"] = "showLiveboardDescription"; + Param["ShowLiveboardTitle"] = "showLiveboardTitle"; + Param["ShowMaskedFilterChip"] = "showMaskedFilterChip"; + Param["IsLiveboardMasterpiecesEnabled"] = "isLiveboardMasterpiecesEnabled"; + Param["EnableNewChartLibrary"] = "muzeChartPhase1EnabledGA"; + Param["HiddenTabs"] = "hideTabs"; + Param["VisibleTabs"] = "visibleTabs"; + Param["HideTabPanel"] = "hideTabPanel"; + Param["HideSampleQuestions"] = "hideSampleQuestions"; + Param["WorksheetId"] = "worksheet"; + Param["Query"] = "query"; + Param["HideHomepageLeftNav"] = "hideHomepageLeftNav"; + Param["ModularHomeExperienceEnabled"] = "modularHomeExperience"; + Param["HomepageVersion"] = "homepageVersion"; + Param["ListPageVersion"] = "listpageVersion"; + Param["PendoTrackingKey"] = "additionalPendoKey"; + Param["LiveboardHeaderSticky"] = "isLiveboardHeaderSticky"; + Param["IsProductTour"] = "isProductTour"; + Param["HideSearchBarTitle"] = "hideSearchBarTitle"; + Param["HideSageAnswerHeader"] = "hideSageAnswerHeader"; + Param["HideSearchBar"] = "hideSearchBar"; + Param["ClientLogLevel"] = "clientLogLevel"; + Param["ExposeTranslationIDs"] = "exposeTranslationIDs"; + Param["OverrideNativeConsole"] = "overrideConsoleLogs"; + Param["enableAskSage"] = "enableAskSage"; + Param["CollapseSearchBarInitially"] = "collapseSearchBarInitially"; + Param["DataPanelCustomGroupsAccordionInitialState"] = "dataPanelCustomGroupsAccordionInitialState"; + Param["EnableCustomColumnGroups"] = "enableCustomColumnGroups"; + Param["DateFormatLocale"] = "dateFormatLocale"; + Param["NumberFormatLocale"] = "numberFormatLocale"; + Param["CurrencyFormat"] = "currencyFormat"; + Param["Enable2ColumnLayout"] = "enable2ColumnLayout"; + Param["IsFullAppEmbed"] = "isFullAppEmbed"; + Param["IsOnBeforeGetVizDataInterceptEnabled"] = "isOnBeforeGetVizDataInterceptEnabled"; + Param["FocusSearchBarOnRender"] = "focusSearchBarOnRender"; + Param["DisableRedirectionLinksInNewTab"] = "disableRedirectionLinksInNewTab"; + Param["HomePageSearchBarMode"] = "homePageSearchBarMode"; + Param["ShowLiveboardVerifiedBadge"] = "showLiveboardVerifiedBadge"; + Param["ShowLiveboardReverifyBanner"] = "showLiveboardReverifyBanner"; + Param["LiveboardHeaderV2"] = "isLiveboardHeaderV2Enabled"; + Param["HideIrrelevantFiltersInTab"] = "hideIrrelevantFiltersAtTabLevel"; + Param["IsEnhancedFilterInteractivityEnabled"] = "isLiveboardPermissionV2Enabled"; + Param["SpotterEnabled"] = "isSpotterExperienceEnabled"; + Param["IsUnifiedSearchExperienceEnabled"] = "isUnifiedSearchExperienceEnabled"; + Param["OverrideOrgId"] = "orgId"; + Param["OauthPollingInterval"] = "oAuthPollingInterval"; + Param["IsForceRedirect"] = "isForceRedirect"; + Param["DataSourceId"] = "dataSourceId"; + Param["preAuthCache"] = "preAuthCache"; + Param["ShowSpotterLimitations"] = "showSpotterLimitations"; + Param["CoverAndFilterOptionInPDF"] = "arePdfCoverFilterPageCheckboxesEnabled"; + Param["PrimaryAction"] = "primaryAction"; + Param["isSpotterAgentEmbed"] = "isSpotterAgentEmbed"; + Param["IsLiveboardStylingAndGroupingEnabled"] = "isLiveboardStylingAndGroupingEnabled"; + Param["IsLazyLoadingForEmbedEnabled"] = "isLazyLoadingForEmbedEnabled"; + Param["RootMarginForLazyLoad"] = "rootMarginForLazyLoad"; + Param["isPNGInScheduledEmailsEnabled"] = "isPNGInScheduledEmailsEnabled"; + Param["IsWYSIWYGLiveboardPDFEnabled"] = "isWYSIWYGLiveboardPDFEnabled"; + Param["isLiveboardXLSXCSVDownloadEnabled"] = "isLiveboardXLSXCSVDownloadEnabled"; + Param["isGranularXLSXCSVSchedulesEnabled"] = "isGranularXLSXCSVSchedulesEnabled"; + Param["isSendNowLiveboardSchedulingEnabled"] = "isSendNowLiveboardSchedulingEnabled"; + Param["isCentralizedLiveboardFilterUXEnabled"] = "isCentralizedLiveboardFilterUXEnabled"; + Param["isLinkParametersEnabled"] = "isLinkParametersEnabled"; + Param["EnablePastConversationsSidebar"] = "enablePastConversationsSidebar"; + Param["UpdatedSpotterChatPrompt"] = "updatedSpotterChatPrompt"; + Param["EnableStopAnswerGenerationEmbed"] = "enableStopAnswerGenerationEmbed"; + Param["SpotterSidebarTitle"] = "spotterSidebarTitle"; + Param["SpotterSidebarDefaultExpanded"] = "spotterSidebarDefaultExpanded"; + Param["SpotterChatRenameLabel"] = "spotterChatRenameLabel"; + Param["SpotterChatDeleteLabel"] = "spotterChatDeleteLabel"; + Param["SpotterDeleteConversationModalTitle"] = "spotterDeleteConversationModalTitle"; + Param["SpotterPastConversationAlertMessage"] = "spotterPastConversationAlertMessage"; + Param["SpotterDocumentationUrl"] = "spotterDocumentationUrl"; + Param["SpotterBestPracticesLabel"] = "spotterBestPracticesLabel"; + Param["SpotterConversationsBatchSize"] = "spotterConversationsBatchSize"; + Param["SpotterNewChatButtonTitle"] = "spotterNewChatButtonTitle"; + Param["IsThisPeriodInDateFiltersEnabled"] = "isThisPeriodInDateFiltersEnabled"; + Param["HideToolResponseCardBranding"] = "hideToolResponseCardBranding"; + Param["ToolResponseCardBrandingLabel"] = "toolResponseCardBrandingLabel"; + Param["EnableHomepageAnnouncement"] = "enableHomepageAnnouncement"; + Param["EnableLiveboardDataCache"] = "enableLiveboardDataCache"; + Param["SpotterFileUploadEnabled"] = "spotterFileUploadEnabled"; + Param["SpotterFileUploadFileTypes"] = "spotterFileUploadFileTypes"; +})(Param || (Param = {})); +/** + * ThoughtSpot application pages include actions and menu commands + * for various user-initiated operations. These actions are represented + * as enumeration members in the SDK. To control actions in the embedded view: + * - disabledActions — the action is grayed out and still visible, but non-interactive (user can see but not click). + * - hiddenActions — the action is completely removed from the UI (user cannot see it at all). + * - visibleActions — allowlist, only these actions are shown; all others are hidden. + * + * Use disabledActions to disable (gray out) an action. + * Use hiddenActions to hide (fully remove) an action. + * Use visibleActions to show only specific actions. + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... //other embed view config + * visibleActions: [Action.Save, Action.Edit, Action.Present, Action.Explore], + * disabledActions: [Action.Download], + * //hiddenActions: [], // Set either this or visibleActions + * }) + * ``` + * @example + * ```js + * const embed = new LiveboardEmbed('#tsEmbed', { + * ... //other embed view config + * //visibleActions: [], + * disabledActions: [Action.Download], + * hiddenActions: [Action.Edit, Action.Explore], + * }) + * ``` + * See also link:https://developers.thoughtspot.com/docs/actions[Developer Documentation]. + */ +var Action; +(function (Action) { + /** + * The **Save** action on an Answer or Liveboard. + * Allows users to save the changes. + * @example + * ```js + * disabledActions: [Action.Save] + * ``` + */ + Action["Save"] = "save"; + /** + * @hidden + */ + Action["Update"] = "update"; + /** + * @hidden + */ + Action["SaveUntitled"] = "saveUntitled"; + /** + * The **Save as View** action on the Answer + * page. Saves an Answer as a View object in the full + * application embedding mode. + * @example + * ```js + * disabledActions: [Action.SaveAsView] + * ``` + */ + Action["SaveAsView"] = "saveAsView"; + /** + * The **Make a copy** action on a Liveboard or Answer + * page. Creates a copy of the Liveboard. + * In LiveboardEmbed, the **Make a copy** action is not available for + * visualizations in the embedded Liveboard view. + * In AppEmbed, the **Make a copy** action is available on both + * Liveboards and visualizations. + * @example + * ```js + * disabledActions: [Action.MakeACopy] + * ``` + */ + Action["MakeACopy"] = "makeACopy"; + /** + * The **Copy and Edit** action on a Liveboard. + * This action is now replaced with `Action.MakeACopy`. + * @example + * ```js + * disabledActions: [Action.EditACopy] + * ``` + */ + Action["EditACopy"] = "editACopy"; + /** + * The **Copy link** menu action on a Liveboard visualization. + * Copies the visualization URL + * @example + * ```js + * disabledActions: [Action.CopyLink] + * ``` + */ + Action["CopyLink"] = "embedDocument"; + /** + * @hidden + */ + Action["ResetLayout"] = "resetLayout"; + /** + * The **Schedule** menu action on a Liveboard. + * Allows scheduling a Liveboard job, for example, + * sending periodic notifications. + * @example + * ```js + * disabledActions: [Action.Schedule] + * ``` + */ + Action["Schedule"] = "subscription"; + /** + * The **Manage schedules** menu action on a Liveboard. + * Allows users to manage scheduled Liveboard jobs. + * @example + * ```js + * disabledActions: [Action.SchedulesList] + * ``` + */ + Action["SchedulesList"] = "schedule-list"; + /** + * The **Share** action on a Liveboard, Answer, or Model. + * Allows users to share an object with other users and groups. + * @example + * ```js + * disabledActions: [Action.Share] + * ``` + */ + Action["Share"] = "share"; + /** + * The **Add filter** action on a Liveboard page. + * Allows adding filters to visualizations on a Liveboard. + * @example + * ```js + * disabledActions: [Action.AddFilter] + * ``` + */ + Action["AddFilter"] = "addFilter"; + /** + * The **Add Data Panel Objects** action on the data panel v2. + * Allows to show action menu to add different objects (such as + * formulas, Parameters) in data panel new experience. + * @example + * ```js + * disabledActions: [Action.AddDataPanelObjects] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl, 10.1.0.sw + */ + Action["AddDataPanelObjects"] = "addDataPanelObjects"; + /** + * The filter configuration options for a Liveboard. + * The configuration options are available when adding + * filters on a Liveboard. + * + * @example + * ```js + * disabledActions: [Action.ConfigureFilter] + * ``` + */ + Action["ConfigureFilter"] = "configureFilter"; + /** + * The **Collapse data sources** icon on the Search page. + * Collapses the panel showing data sources. + * + * @example + * ```js + * disabledActions: [Action.CollapseDataPanel] + * ``` + * @version SDK: 1.1.0 | ThoughtSpot Cloud: ts7.may.cl, 8.4.1.sw + */ + Action["CollapseDataSources"] = "collapseDataSources"; + /** + * The **Collapse data panel** icon on the Search page. + * Collapses the data panel view. + * + * @version SDK: 1.34.0 | ThoughtSpot Cloud: 10.3.0.cl + * + * @example + * ```js + * disabledActions: [Action.CollapseDataPanel] + * ``` + */ + Action["CollapseDataPanel"] = "collapseDataPanel"; + /** + * The **Choose sources** button on Search page. + * Allows selecting data sources for search queries. + * @example + * ```js + * disabledActions: [Action.ChooseDataSources] + * ``` + */ + Action["ChooseDataSources"] = "chooseDataSources"; + /** + * The **Create formula** action on a Search or Answer page. + * Allows adding formulas to an Answer. + * @example + * ```js + * disabledActions: [Action.AddFormula] + * ``` + */ + Action["AddFormula"] = "addFormula"; + /** + * The **Add parameter** action on a Liveboard or Answer. + * Allows adding Parameters to a Liveboard or Answer. + * @example + * ```js + * disabledActions: [Action.AddParameter] + * ``` + */ + Action["AddParameter"] = "addParameter"; + /** + * The **Add Column Set** action on a Answer. + * Allows adding column sets to a Answer. + * @example + * ```js + * disabledActions: [Action.AddColumnSet] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl, 10.1.0.sw + */ + Action["AddColumnSet"] = "addSimpleCohort"; + /** + * The **Add Query Set** action on a Answer. + * Allows adding query sets to a Answer. + * @example + * ```js + * disabledActions: [Action.AddQuerySet] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl, 10.1.0.sw + */ + Action["AddQuerySet"] = "addAdvancedCohort"; + /** + * @hidden + */ + Action["SearchOnTop"] = "searchOnTop"; + /** + * The **SpotIQ analyze** menu action on a visualization or + * Answer page. + * @example + * ```js + * disabledActions: [Action.SpotIQAnalyze] + * ``` + */ + Action["SpotIQAnalyze"] = "spotIQAnalyze"; + /** + * @hidden + */ + Action["ExplainInsight"] = "explainInsight"; + /** + * @hidden + */ + Action["SpotIQFollow"] = "spotIQFollow"; + /** + * The Share action for a Liveboard visualization. + */ + Action["ShareViz"] = "shareViz"; + /** + * @hidden + */ + Action["ReplaySearch"] = "replaySearch"; + /** + * The **Show underlying data** menu action on a + * visualization or Answer page. + * Displays detailed information and raw data + * for a given visualization. + * @example + * ```js + * disabledActions: [Action.ShowUnderlyingData] + * ``` + */ + Action["ShowUnderlyingData"] = "showUnderlyingData"; + /** + * The **Download** menu action on Liveboard + * visualizations and Answers. + * Allows downloading a visualization or Answer. + * @example + * ```js + * disabledActions: [Action.DownloadAsPng] + * ``` + */ + Action["Download"] = "download"; + /** + * The **Download** > **PNG** menu action for charts on a Liveboard + * or Answer page. + * Downloads a visualization or Answer as a PNG file. + * @example + * ```js + * disabledActions: [Action.DownloadAsPng] + * ``` + */ + Action["DownloadAsPng"] = "downloadAsPng"; + /** + * + *The **Download PDF** action that downloads a Liveboard, + * visualization, or Answer as a PDF file. + * + * **NOTE**: The **Download** > **PDF** option is available for + * tables in visualizations and Answers. + * @example + * ```js + * disabledActions: [Action.DownloadAsPdf] + * ``` + */ + Action["DownloadAsPdf"] = "downloadAsPdf"; + /** + * The **Download** > **CSV** menu action for tables on a Liveboard + * or Answer page. + * Downloads a visualization or Answer in the XLSX format. + * @example + * ```js + * disabledActions: [Action.DownloadAsCsv] + * ``` + */ + Action["DownloadAsCsv"] = "downloadAsCSV"; + /** + * The **Download** > **XLSX** menu action for tables on a Liveboard + * or Answer page. + * Downloads a visualization or Answer in the XLSX format. + * @example + * ```js + * disabledActions: [Action.DownloadAsXlsx] + * ``` + */ + Action["DownloadAsXlsx"] = "downloadAsXLSX"; + /** + * The **Download Liveboard** menu action on a Liveboard. + * Allows downloading the entire Liveboard. + * @example + * ```js + * disabledActions: [Action.DownloadLiveboard] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + Action["DownloadLiveboard"] = "downloadLiveboard"; + /** + * The **Download Liveboard as Continuous PDF** menu action on a Liveboard. + * Allows downloading the entire Liveboard as a continuous PDF. + * @example + * ```js + * disabledActions: [Action.DownloadLiveboardAsContinuousPDF] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + Action["DownloadLiveboardAsContinuousPDF"] = "downloadLiveboardAsContinuousPDF"; + /** + * The Download Liveboard as A4 PDF menu action on a Liveboard. + * Allows downloading the entire Liveboard as an A4 PDF. + * Requires {@link Action.DownloadLiveboard} as a parent action when + * {@link LiveboardViewConfig.isLiveboardXLSXCSVDownloadEnabled} or + * {@link LiveboardViewConfig.isContinuousLiveboardPDFEnabled} flags are enabled. + * Use this instead of {@link Action.DownloadAsPdf} when either flag is on. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * disabledActions: [Action.DownloadLiveboardAsA4Pdf] + * ``` + */ + Action["DownloadLiveboardAsA4Pdf"] = "downloadLiveboardAsA4Pdf"; + /** + * The **Download Liveboard as XLSX** menu action on a Liveboard. + * Allows downloading the entire Liveboard as an XLSX file. + * @example + * ```js + * disabledActions: [Action.DownloadLiveboardAsXlsx] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + Action["DownloadLiveboardAsXlsx"] = "downloadLiveboardAsXlsx"; + /** + * The **Download Liveboard as CSV** menu action on a Liveboard. + * Allows downloading the entire Liveboard as a CSV file. + * @example + * ```js + * disabledActions: [Action.DownloadLiveboardAsCsv] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + Action["DownloadLiveboardAsCsv"] = "downloadLiveboardAsCsv"; + /** + * @hidden + */ + Action["DownloadTrace"] = "downloadTrace"; + /** + * The **Export TML** menu action on a Liveboard, Answer, and + * the Data Workspace pages for data objects and connections. + * + * Allows exporting an object as a TML file. + * + * @example + * ```js + * disabledActions: [Action.ExportTML] + * ``` + */ + Action["ExportTML"] = "exportTSL"; + /** + * The **Import TML** menu action on the + * *Data Workspace* > *Utilities* page. + * Imports TML representation of ThoughtSpot objects. + * @example + * ```js + * disabledActions: [Action.ImportTML] + * ``` + */ + Action["ImportTML"] = "importTSL"; + /** + * The **Update TML** menu action for Liveboards and Answers. + * Updates TML representation of ThoughtSpot objects. + * @example + * ```js + * disabledActions: [Action.UpdateTML] + * ``` + */ + Action["UpdateTML"] = "updateTSL"; + /** + * The **Edit TML** menu action for Liveboards and Answers. + * Opens the TML editor. + * @example + * ```js + * disabledActions: [Action.EditTML] + * ``` + */ + Action["EditTML"] = "editTSL"; + /** + * The **Present** menu action for Liveboards and Answers. + * Allows presenting a Liveboard or visualization in + * slideshow mode. + * @example + * ```js + * disabledActions: [Action.Present] + * ``` + */ + Action["Present"] = "present"; + /** + * The visualization tile resize option. + * Also available via More `...` options menu on a visualization. + * Allows resizing visualization tiles and switching + * between different preset layout option. + * + * @example + * ```js + * disabledActions: [Action.ToggleSize] + * ``` + */ + Action["ToggleSize"] = "toggleSize"; + /** + * The *Edit* action on the Liveboard page and in the + * visualization menu. + * Opens a Liveboard or visualization in edit mode. + * @example + * ```js + * disabledActions: [Action.Edit] + * ``` + */ + Action["Edit"] = "edit"; + /** + * The text edit option for Liveboard and visualization titles. + * @example + * ```js + * disabledActions: [Action.EditTitle] + * ``` + */ + Action["EditTitle"] = "editTitle"; + /** + * The **Delete** action on a Liveboard, *Liveboards* and + * *Answers* list pages in full application embedding. + * + * @example + * ```js + * disabledActions: [Action.Remove] + * ``` + */ + Action["Remove"] = "delete"; + /** + * @hidden + */ + Action["Ungroup"] = "ungroup"; + /** + * @hidden + */ + Action["Describe"] = "describe"; + /** + * @hidden + */ + Action["Relate"] = "relate"; + /** + * @hidden + */ + Action["CustomizeHeadlines"] = "customizeHeadlines"; + /** + * @hidden + */ + Action["PinboardInfo"] = "pinboardInfo"; + /** + * The **Show Liveboard details** menu action on a Liveboard. + * Displays details such as the name, description, and + * author of the Liveboard, and timestamp of Liveboard creation + * and update. + * @example + * ```js + * disabledActions: [Action.LiveboardInfo] + * ``` + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + Action["LiveboardInfo"] = "pinboardInfo"; + /** + * @hidden + */ + Action["SendAnswerFeedback"] = "sendFeedback"; + /** + * @hidden + */ + Action["DownloadEmbraceQueries"] = "downloadEmbraceQueries"; + /** + * The **Pin** menu action on an Answer or + * Search results page. + * @example + * ```js + * disabledActions: [Action.Pin] + * ``` + */ + Action["Pin"] = "pin"; + /** + * @hidden + */ + Action["AnalysisInfo"] = "analysisInfo"; + /** + * The **Schedule** menu action on a Liveboard. + * Allows scheduling a Liveboard job. + * @example + * ```js + * disabledActions: [Action.Subscription] + * ``` + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + Action["Subscription"] = "subscription"; + /** + * The **Explore** action on Liveboard visualizations + * @example + * ```js + * disabledActions: [Action.Explore] + * ``` + */ + Action["Explore"] = "explore"; + /** + * The contextual menu action to include a specific data point + * when drilling down a table or chart on an Answer. + * + * @example + * ```js + * disabledActions: [Action.DrillInclude] + * ``` + */ + Action["DrillInclude"] = "context-menu-item-include"; + /** + * The contextual menu action to exclude a specific data point + * when drilling down a table or chart on an Answer. + * @example + * ```js + * disabledActions: [Action.DrillInclude] + * ``` + */ + Action["DrillExclude"] = "context-menu-item-exclude"; + /** + * The **Copy to clipboard** menu action on tables in an Answer + * or Liveboard. + * Copies the selected data point. + * @example + * ```js + * disabledActions: [Action.CopyToClipboard] + * ``` + */ + Action["CopyToClipboard"] = "context-menu-item-copy-to-clipboard"; + Action["CopyAndEdit"] = "context-menu-item-copy-and-edit"; + /** + * @hidden + */ + Action["DrillEdit"] = "context-menu-item-edit"; + Action["EditMeasure"] = "context-menu-item-edit-measure"; + Action["Separator"] = "context-menu-item-separator"; + /** + * The **Drill down** menu action on Answers and Liveboard + * visualizations. + * Allows drilling down to a specific data point on a chart or table. + * @example + * ```js + * disabledActions: [Action.DrillDown] + * ``` + */ + Action["DrillDown"] = "DRILL"; + /** + * The request access action on Liveboards. + * Allows users with view permissions to request edit access to a Liveboard. + * @example + * ```js + * disabledActions: [Action.RequestAccess] + * ``` + */ + Action["RequestAccess"] = "requestAccess"; + /** + * Controls the display and availability of the **Query visualizer** and + * **Query SQL** buttons in the Query details panel on the Answer page. + * + * **Query visualizer** - Displays the tables and filters used in the search query. + * **Query SQL** - Displays the SQL statements used to retrieve data for the query. + * + * Note: This action ID only affects the visibility of the buttons within the + * Query details panel. It does not control the visibility + * of the query details icon on the Answer page. + * @example + * ```js + * disabledActions: [Action.QueryDetailsButtons] + * ``` + */ + Action["QueryDetailsButtons"] = "queryDetailsButtons"; + /** + * The **Delete** action for Answers in the full application + * embedding mode. + * @example + * ```js + * disabledActions: [Action.AnswerDelete] + * ``` + * @version SDK: 1.9.0 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + */ + Action["AnswerDelete"] = "onDeleteAnswer"; + /** + * The chart switcher icon on Answer page and + * visualizations in edit mode. + * Allows switching to the table or chart mode + * when editing a visualization. + * @example + * ```js + * disabledActions: [Action.AnswerChartSwitcher] + * ``` + * @version SDK: 1.9.0 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + */ + Action["AnswerChartSwitcher"] = "answerChartSwitcher"; + /** + * The Favorites icon (*) for Answers, + * Liveboard, and data objects like Model, + * Tables and Views. + * Allows adding an object to the user's favorites list. + * @example + * ```js + * disabledActions: [Action.AddToFavorites] + * ``` + * @version SDK: 1.9.0 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + */ + Action["AddToFavorites"] = "addToFavorites"; + /** + * The edit icon on Liveboards (Classic experience). + * @example + * ```js + * disabledActions: [Action.EditDetails] + * ``` + * @version SDK: 1.9.0 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw + */ + Action["EditDetails"] = "editDetails"; + /** + * The *Create alert* action for KPI charts. + * Allows users to schedule threshold-based alerts + * for KPI charts. + * @example + * ```js + * disabledActions: [Action.CreateMonitor] + * ``` + * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + */ + Action["CreateMonitor"] = "createMonitor"; + /** + * @version SDK: 1.11.1 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw + * @deprecated This action is deprecated. It was used for reporting errors. + * @example + * ```js + * disabledActions: [Action.ReportError] + * ``` + */ + Action["ReportError"] = "reportError"; + /** + * The **Sync to sheets** action on Answers and Liveboard visualizations. + * Allows sending data to a Google Sheet. + * @example + * ```js + * disabledActions: [Action.SyncToSheets] + * ``` + * @version SDK: 1.18.0 | ThoughtSpot: 8.10.0.cl, 9.0.1.sw + */ + Action["SyncToSheets"] = "sync-to-sheets"; + /** + * The **Sync to other apps** action on Answers and Liveboard visualizations. + * Allows sending data to third-party apps like Slack, Salesforce, + * Microsoft Teams, and so on. + * @example + * ```js + * disabledActions: [Action.SyncToOtherApps] + * ``` + * @version SDK: 1.18.0 | ThoughtSpot: 8.10.0.cl, 9.0.1.sw + */ + Action["SyncToOtherApps"] = "sync-to-other-apps"; + /** + * The **Manage pipelines** action on Answers and Liveboard visualizations. + * Allows users to manage data sync pipelines to third-party apps. + * @example + * ```js + * disabledActions: [Action.ManagePipelines] + * ``` + * @version SDK: 1.18.0 | ThoughtSpot: 8.10.0.cl, 9.0.1.sw + */ + Action["ManagePipelines"] = "manage-pipeline"; + /** + * The **Filter** action on Liveboard visualizations. + * Allows users to apply cross-filters on a Liveboard. + * @example + * ```js + * disabledActions: [Action.CrossFilter] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.8.0.sw + */ + Action["CrossFilter"] = "context-menu-item-cross-filter"; + /** + * The **Sync to Slack** action on Liveboard visualizations. + * Allows sending data to third-party apps like Slack. + * @example + * ```js + * disabledActions: [Action.SyncToSlack] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot Cloud: 10.1.0.cl + */ + Action["SyncToSlack"] = "syncToSlack"; + /** + * The **Sync to Teams** action on Liveboard visualizations. + * Allows sending data to third-party apps like Microsoft Teams. + * @example + * ```js + * disabledActions: [Action.SyncToTeams] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot Cloud: 10.1.0.cl + */ + Action["SyncToTeams"] = "syncToTeams"; + /** + * The **Remove** action that appears when cross filters are applied + * on a Liveboard. + * Removes filters applied to a visualization. + * @example + * ```js + * disabledActions: [Action.RemoveCrossFilter] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["RemoveCrossFilter"] = "context-menu-item-remove-cross-filter"; + /** + * The **Aggregate** option in the chart axis or the + * table column customization menu. + * Provides aggregation options to analyze the data on a chart or table. + * @example + * ```js + * disabledActions: [Action.AxisMenuAggregate] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuAggregate"] = "axisMenuAggregate"; + /** + * The **Time bucket** option in the chart axis or table column + * customization menu. + * Allows defining time metric for date comparison. + * @example + * ```js + * disabledActions: [Action.AxisMenuTimeBucket] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuTimeBucket"] = "axisMenuTimeBucket"; + /** + * The **Filter** action in the chart axis or table column + * customization menu. + * Allows adding, editing, or removing filters. + * + * @example + * ```js + * disabledActions: [Action.AxisMenuFilter] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuFilter"] = "axisMenuFilter"; + /** + * The **Conditional formatting** action on chart or table. + * Allows adding rules for conditional formatting of data + * points on a chart or table. + * @example + * ```js + * disabledActions: [Action.AxisMenuConditionalFormat] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuConditionalFormat"] = "axisMenuConditionalFormat"; + /** + * The **Sort** menu action on a table or chart axis + * Sorts data in ascending or descending order. + * Allows adding, editing, or removing filters. + * @example + * ```js + * disabledActions: [Action.AxisMenuConditionalFormat] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuSort"] = "axisMenuSort"; + /** + * The **Group** option in the chart axis or table column + * customization menu. + * Allows grouping data points if the axes use the same + * unit of measurement and a similar scale. + * @example + * ```js + * disabledActions: [Action.AxisMenuGroup] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuGroup"] = "axisMenuGroup"; + /** + * The **Position** option in the axis customization menu. + * Allows changing the position of the axis to the + * left or right side of the chart. + * @example + * ```js + * disabledActions: [Action.AxisMenuPosition] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuPosition"] = "axisMenuPosition"; + /** + * The **Rename** option in the chart axis or table column customization menu. + * Renames the axis label on a chart or the column header on a table. + * @example + * ```js + * disabledActions: [Action.AxisMenuRename] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuRename"] = "axisMenuRename"; + /** + * The **Edit** action in the axis customization menu. + * Allows editing the axis name, position, minimum and maximum values, + * and format of a column. + * @example + * ```js + * disabledActions: [Action.AxisMenuEdit] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuEdit"] = "axisMenuEdit"; + /** + * The **Number format** action to customize the format of + * the data labels on a chart or table. + * @example + * ```js + * disabledActions: [Action.AxisMenuNumberFormat] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuNumberFormat"] = "axisMenuNumberFormat"; + /** + * The **Text wrapping** action on a table. + * Wraps or clips column text on a table. + * @example + * ```js + * disabledActions: [Action.AxisMenuTextWrapping] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuTextWrapping"] = "axisMenuTextWrapping"; + /** + * The **Remove** action in the chart axis or table column + * customization menu. + * Removes the data labels from a chart or the column of a + * table visualization. + * @example + * ```js + * disabledActions: [Action.AxisMenuRemove] + * ``` + * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw + */ + Action["AxisMenuRemove"] = "axisMenuRemove"; + /** + * The **Compare with** action in the chart axis customization menu. + * Allows comparing data across dimensions or measures. + * @example + * ```js + * disabledActions: [Action.AxisMenuCompare] + * ``` + * @version SDK: 1.50.0 | ThoughtSpot: 26.7.0.cl + */ + Action["AxisMenuCompare"] = "axisMenuCompare"; + /** + * The **Merge with** action in the chart axis customization menu. + * Allows merging data across dimensions or measures. + * @example + * ```js + * disabledActions: [Action.AxisMenuMerge] + * ``` + * @version SDK: 1.50.0 | ThoughtSpot: 26.7.0.cl + */ + Action["AxisMenuMerge"] = "axisMenuMerge"; + /** + * @hidden + */ + Action["InsertInToSlide"] = "insertInToSlide"; + /** + * The **Rename** menu action on Liveboards and visualizations. + * Allows renaming a Liveboard or visualization. + * @example + * ```js + * disabledActions: [Action.RenameModalTitleDescription] + * ``` + * @version SDK: 1.23.0 | ThoughtSpot: 9.4.0.cl, 9.8.0.sw + */ + Action["RenameModalTitleDescription"] = "renameModalTitleDescription"; + /** + * The *Request verification* action on a Liveboard. + * Initiates a request for Liveboard verification. + * @example + * ```js + * disabledActions: [Action.RequestVerification] + * ``` + * @version SDK: 1.25.0 | ThoughtSpot: 9.6.0.cl, 10.1.0.sw + */ + Action["RequestVerification"] = "requestVerification"; + /** + * + * Allows users to mark a Liveboard as verified. + * @example + * ```js + * disabledActions: [Action.MarkAsVerified] + * ``` + * @version SDK: 1.25.0 | ThoughtSpot: 9.6.0.cl, 10.1.0.sw + */ + Action["MarkAsVerified"] = "markAsVerified"; + /** + * The **Add Tab** action on a Liveboard. + * Allows adding a new tab to a Liveboard view. + * @example + * ```js + * disabledActions: [Action.AddTab] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 9.8.0.sw + */ + Action["AddTab"] = "addTab"; + /** + * + * Initiates contextual change analysis on KPI charts. + * @example + * ```js + * disabledActions: [Action.EnableContextualChangeAnalysis] + * ``` + * @version SDK: 1.25.0 | ThoughtSpot Cloud: 9.6.0.cl + */ + Action["EnableContextualChangeAnalysis"] = "enableContextualChangeAnalysis"; + /** + * Action ID to hide or disable Iterative Change Analysis option + * in the contextual change analysis Insight charts context menu. + * + * @example + * ```js + * disabledActions: [Action.EnableIterativeChangeAnalysis] + * ``` + * @version SDK: 1.41.0 | ThoughtSpot Cloud: 9.12.0.cl + */ + Action["EnableIterativeChangeAnalysis"] = "enableIterativeChangeAnalysis"; + /** + * Action ID to hide or disable Natural Language Search query. + * + * @example + * ```js + * disabledActions: [Action.ShowSageQuery] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot Cloud: 9.7.0.cl + */ + Action["ShowSageQuery"] = "showSageQuery"; + /** + * + * Action ID to hide or disable the edit option for the + * results generated from the + * Natural Language Search query. + * + * @example + * ```js + * disabledActions: [Action.EditSageAnswer] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot Cloud: 9.7.0.cl + */ + Action["EditSageAnswer"] = "editSageAnswer"; + /** + * The feedback widget for AI-generated Answers. + * Allows users to send feedback on the Answers generated + * from a Natural Language Search query. + * + * @example + * ```js + * disabledActions: [Action.SageAnswerFeedback] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl + */ + Action["SageAnswerFeedback"] = "sageAnswerFeedback"; + /** + * + * @example + * ```js + * disabledActions: [Action.ModifySageAnswer] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl + */ + Action["ModifySageAnswer"] = "modifySageAnswer"; + /** + * The **Move to Tab** menu action on visualizations in Liveboard edit mode. + * Allows moving a visualization to a different tab. + * @example + * ```js + * disabledActions: [Action.MoveToTab] + * ``` + */ + Action["MoveToTab"] = "onContainerMove"; + /** + * The **Manage Alerts** menu action on KPI visualizations. + * Allows creating, viewing, and editing monitor + * alerts for a KPI chart. + * + * @example + * ```js + * disabledActions: [Action.ManageMonitor] + * ``` + */ + Action["ManageMonitor"] = "manageMonitor"; + /** + * The Liveboard Personalised Views dropdown. + * Allows navigating to a personalized Liveboard View. + * This action is deprecated. Use {@link Action.PersonalizedViewsDropdown} instead. + * @example + * ```js + * disabledActions: [Action.PersonalisedViewsDropdown] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 10.1.0.sw + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + Action["PersonalisedViewsDropdown"] = "personalisedViewsDropdown"; + /** + * The Liveboard Personalized Views dropdown. + * Allows navigating to a personalized Liveboard View. + * @example + * ```js + * disabledActions: [Action.PersonalizedViewsDropdown] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + Action["PersonalizedViewsDropdown"] = "personalisedViewsDropdown"; + /** + * Action ID for show or hide the user details on a + * Liveboard (Recently visited / social proof) + * @example + * ```js + * disabledActions: [Action.LiveboardUsers] + * ``` + * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 10.1.0.sw + */ + Action["LiveboardUsers"] = "liveboardUsers"; + /** + * Action ID for the Parent TML action + * The parent action **TML** must be included to access TML-related options + * within the cascading menu (specific to the Answer page) + * @example + * ```js + * // to include specific TML actions + * visibleActions: [Action.TML, Action.ExportTML, Action.EditTML] + * + * ``` + * @example + * ```js + * hiddenAction: [Action.TML] // hide all TML actions + * disabledActions: [Action.TML] // to disable all TML actions + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: 9.12.0.cl, 10.1.0.sw + */ + Action["TML"] = "tml"; + /** + * The **Create Liveboard* action on + * the Liveboards page and the Pin modal. + * Allows users to create a Liveboard. + * + * @example + * ```js + * hiddenAction: [Action.CreateLiveboard] + * disabledActions: [Action.CreateLiveboard] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot: 10.1.0.cl, 10.1.0.sw + */ + Action["CreateLiveboard"] = "createLiveboard"; + /** + * Action ID for to hide or disable the + * Verified Liveboard banner. + * @example + * ```js + * hiddenAction: [Action.VerifiedLiveboard] + * ``` + * @version SDK: 1.29.0 | ThoughtSpot: 9.10.0.cl, 10.1.0.sw + */ + Action["VerifiedLiveboard"] = "verifiedLiveboard"; + /** + * Action ID for the *Ask Sage* In Natural Language Search embed, + * *Spotter* in Liveboard, full app, and Spotter embed. + * + * Allows initiating a conversation with ThoughtSpot AI analyst. + * + * @example + * ```js + * hiddenAction: [Action.AskAi] + * ``` + * @version SDK: 1.29.0 | ThoughtSpot Cloud: 9.12.0.cl + */ + Action["AskAi"] = "AskAi"; + /** + * The **Add KPI to Watchlist** action on Home page watchlist. + * Adds a KPI chart to the watchlist on the Home page. + * @example + * ```js + * disabledActions: [Action.AddToWatchlist] + * ``` + * @version SDK: 1.27.9 | ThoughtSpot Cloud: 9.12.5.cl + */ + Action["AddToWatchlist"] = "addToWatchlist"; + /** + * The **Remove from watchlist** menu action on KPI watchlist. + * Removes a KPI chart from the watchlist on the Home page. + * @example + * ```js + * disabledActions: [Action.RemoveFromWatchlist] + * ``` + * @version SDK: 1.27.9 | ThoughtSpot: 9.12.5.cl + */ + Action["RemoveFromWatchlist"] = "removeFromWatchlist"; + /** + * The **Organize Favourites** action on Homepage + * *Favorites* module. + * This action is deprecated. Use {@link Action.OrganizeFavorites} instead. + * + * @example + * ```js + * disabledActions: [Action.OrganiseFavourites] + * ``` + * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl + * @deprecated SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + Action["OrganiseFavourites"] = "organiseFavourites"; + /** + * The **Organize Favorites** action on Homepage + * *Favorites* module. + * + * @example + * ```js + * disabledActions: [Action.OrganizeFavorites] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl + */ + Action["OrganizeFavorites"] = "organiseFavourites"; + /** + * The **AI Highlights** action on a Liveboard. + * + * @example + * ```js + * hiddenAction: [Action.AIHighlights] + * ``` + * @version SDK: 1.27.10 | ThoughtSpot Cloud: 9.12.5.cl + */ + Action["AIHighlights"] = "AIHighlights"; + /** + * The *Edit* action on the *Liveboard Schedules* page + * (new Homepage experience). + * Allows editing Liveboard schedules. + * + * @example + * ```js + * disabledActions: [Action.EditScheduleHomepage] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot Cloud: 10.3.0.cl + */ + Action["EditScheduleHomepage"] = "editScheduleHomepage"; + /** + * The *Pause* action on the *Liveboard Schedules* page + * Pauses a scheduled Liveboard job. + * @example + * ```js + * disabledActions: [Action.PauseScheduleHomepage] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot Cloud: 10.3.0.cl + */ + Action["PauseScheduleHomepage"] = "pauseScheduleHomepage"; + /** + * The **View run history** action **Liveboard Schedules** page. + * Allows viewing schedule run history. + * @example + * ```js + * disabledActions: [Action.ViewScheduleRunHomepage] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot: 10.3.0.cl + */ + Action["ViewScheduleRunHomepage"] = "viewScheduleRunHomepage"; + /** + * Action ID to hide or disable the + * unsubscribe option for Liveboard schedules. + * @example + * ```js + * disabledActions: [Action.UnsubscribeScheduleHomepage] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot: 10.3.0.cl + */ + Action["UnsubscribeScheduleHomepage"] = "unsubscribeScheduleHomepage"; + /** + * The **Manage Tags** action on Homepage Favourite Module. + * @example + * ```js + * disabledActions: [Action.ManageTags] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot Cloud: 10.3.0.cl + */ + Action["ManageTags"] = "manageTags"; + /** + * The **Delete** action on the **Liveboard Schedules* page. + * Deletes a Liveboard schedule. + * @example + * ```js + * disabledActions: [Action.DeleteScheduleHomepage] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot: 10.3.0.cl + */ + Action["DeleteScheduleHomepage"] = "deleteScheduleHomepage"; + /** + * The **Analyze CTA** action on KPI chart. + * @example + * ```js + * disabledActions: [Action.KPIAnalysisCTA] + * ``` + * @version SDK: 1.34.0 | ThoughtSpot Cloud: 10.3.0.cl + */ + Action["KPIAnalysisCTA"] = "kpiAnalysisCTA"; + /** + * Action ID for disabling chip reorder in Answer and Liveboard + * @example + * ```js + * const disabledActions = [Action.DisableChipReorder] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + Action["DisableChipReorder"] = "disableChipReorder"; + /** + * Action ID to show, hide, or disable filters + * in a Liveboard tab. + * + * @example + * ```js + * hiddenAction: [Action.ChangeFilterVisibilityInTab] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + Action["ChangeFilterVisibilityInTab"] = "changeFilterVisibilityInTab"; + /** + * The **Data model instructions** button on the Spotter interface. + * Allows opening the data model instructions modal. + * + * @example + * ```js + * hiddenAction: [Action.DataModelInstructions] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["DataModelInstructions"] = "DataModelInstructions"; + /** + * The **Preview data** button on the Spotter interface. + * Allows previewing the data used for Spotter queries. + * + * @example + * ```js + * hiddenAction: [Action.PreviewDataSpotter] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + Action["PreviewDataSpotter"] = "previewDataSpotter"; + /** + * The **Reset** link on the Spotter interface. + * Resets the conversation with Spotter. + * + * @example + * ```js + * hiddenAction: [Action.ResetSpotterChat] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + Action["ResetSpotterChat"] = "resetSpotterChat"; + /** + * Action ID for hide or disable the + * Spotter feedback widget. + * + * @example + * ```js + * hiddenAction: [Action.SpotterFeedback] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + Action["SpotterFeedback"] = "spotterFeedback"; + /** + * Action ID for hide or disable + * the previous prompt edit option in Spotter. + * + * @example + * ```js + * hiddenAction: [Action.EditPreviousPrompt] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + Action["EditPreviousPrompt"] = "editPreviousPrompt"; + /** + * Action ID for hide or disable + * the previous prompt deletion option in Spotter. + * + * @example + * ```js + * hiddenAction: [Action.DeletePreviousPrompt] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + Action["DeletePreviousPrompt"] = "deletePreviousPrompt"; + /** + * Action ID for hide or disable editing tokens generated from + * Spotter results. + * @example + * ```js + * hiddenAction: [Action.EditTokens] + * ``` + * @version SDK: 1.36.0 | ThoughtSpot Cloud: 10.6.0.cl + */ + Action["EditTokens"] = "editTokens"; + /** + * Action ID for hiding rename option for Column rename + * @example + * ```js + * hiddenAction: [Action.ColumnRename] + * ``` + * @version SDK: 1.37.0 | ThoughtSpot Cloud: 10.8.0.cl + */ + Action["ColumnRename"] = "columnRename"; + /** + * Action ID for hide checkboxes for include or exclude + * cover and filter pages in the Liveboard PDF + * @example + * ```js + * hiddenAction: [Action.CoverAndFilterOptionInPDF] + * ``` + * @version SDK: 1.37.0 | ThoughtSpot Cloud: 10.8.0.cl + */ + Action["CoverAndFilterOptionInPDF"] = "coverAndFilterOptionInPDF"; + /** + * Action ID to hide or disable the Coaching workflow in Spotter conversations. + * When disabled, users cannot access **Add to Coaching** workflow in conversation. + * The **Add to Coaching** feature allows adding reference questions and + * business terms to improve Spotter’s responses. This feature is generally available + * (GA) from version 26.2.0.cl and enabled by default on embed deployments. + * @example + * ```js + * hiddenAction: [Action.InConversationTraining] + * disabledActions: [Action.InConversationTraining] + * + * ``` + * @version SDK: 1.39.0 | ThoughtSpot Cloud: 10.10.0.cl + */ + Action["InConversationTraining"] = "InConversationTraining"; + /** + * Action ID to hide the warnings banner in + * Spotter results. It's an EA feature and + * handled by LD. + * @example + * ```js + * hiddenAction: [Action.SpotterWarningsBanner] + * ``` + * @version SDK: 1.41.0 | ThoughtSpot Cloud: 10.13.0.cl + */ + Action["SpotterWarningsBanner"] = "SpotterWarningsBanner"; + /** + * Action ID to hide the warnings border on the knowledge + * card in Spotter results. It's an EA feature and + * handled by LD. + * @example + * ```js + * hiddenAction: [Action.SpotterWarningsOnTokens] + * ``` + * @version SDK: 1.41.0 | ThoughtSpot Cloud: 10.13.0.cl + */ + Action["SpotterWarningsOnTokens"] = "SpotterWarningsOnTokens"; + /** + * Action ID to disable the click event handler on knowledge + * card in Spotter results. It's an EA feature and + * handled by LD. + * @example + * ```js + * hiddenAction: [Action.SpotterTokenQuickEdit] + * ``` + * @version SDK: 1.41.0 | ThoughtSpot Cloud: 10.13.0.cl + */ + Action["SpotterTokenQuickEdit"] = "SpotterTokenQuickEdit"; + /** + * The **PNG screenshot in email** option in the schedule email dialog. + * Includes a PNG screenshot in the notification email body. + * @example + * ```js + * disabledActions: [Action.PngScreenshotInEmail] + * ``` + * ``` + * @version SDK: 1.42.0 | ThoughtSpot Cloud: 10.14.0.cl + */ + Action["PngScreenshotInEmail"] = "pngScreenshotInEmail"; + /** + * The **Remove attachment** action in the schedule email dialog. + * Removes an attachment from the email configuration. + * @example + * ```js + * disabledActions: [Action.RemoveAttachment] + * ``` + * ``` + * ``` + * @version SDK: 1.42.0 | ThoughtSpot Cloud: 10.14.0.cl + */ + Action["RemoveAttachment"] = "removeAttachment"; + /** + * The **Style panel** on a Liveboard. + * Controls the visibility of the Liveboard style panel. + * @example + * ```js + * hiddenActions: [Action.LiveboardStylePanel] + * ``` + * @version SDK: 1.43.0 | ThoughtSpot Cloud: 10.15.0.cl + */ + Action["LiveboardStylePanel"] = "liveboardStylePanel"; + /** + * The **Publish** action for Liveboards, Answers and Models. + * Opens the publishing modal. It's a parent action for the + * **Manage Publishing** and **Unpublish** actions if the object + * is already published, otherwise appears standalone. + * @example + * ```js + * hiddenActions: [Action.Publish] + * disabledActions: [Action.Publish] + * ``` + * @version SDK: 1.45.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Action["Publish"] = "publish"; + /** + * The **Manage Publishing** action for Liveboards, Answers and Models. + * Opens the same publishing modal as the **Publish** action. + * Appears as a child action to the **Publish** action if the + * object is already published. + * @example + * ```js + * hiddenActions: [Action.ManagePublishing] + * disabledActions: [Action.ManagePublishing] + * ``` + * @version SDK: 1.45.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Action["ManagePublishing"] = "managePublishing"; + /** + * The **Unpublish** action for Liveboards, Answers and Models. + * Opens the unpublishing modal. Appears as a child action to + * the **Publish** action if the object is already published. + * @example + * ```js + * hiddenActions: [Action.Unpublish] + * disabledActions: [Action.Unpublish] + * ``` + * @version SDK: 1.45.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Action["Unpublish"] = "unpublish"; + /** + * The **Parameterize** action for Tables and Connections. + * Opens the parameterization modal. + * @example + * ```js + * hiddenActions: [Action.Parameterize] + * disabledActions: [Action.Parameterize] + * ``` + * @version SDK: 1.45.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Action["Parameterize"] = "parameterise"; + /** + * The **Move to Group** menu action on a Liveboard. + * Allows moving a visualization to a different group. + * @example + * ```js + * disabledActions: [Action.MoveToGroup] + * ``` + * @version SDK: 1.44.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Action["MoveToGroup"] = "moveToGroup"; + /** + * The **Move out of Group** menu action on a Liveboard. + * Allows moving a visualization out of a group. + * @example + * ```js + * disabledActions: [Action.MoveOutOfGroup] + * ``` + * @version SDK: 1.44.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Action["MoveOutOfGroup"] = "moveOutOfGroup"; + /** + * The **Create Group** menu action on a Liveboard. + * Allows creating a new group. + * @example + * ```js + * disabledActions: [Action.CreateGroup] + * ``` + * @version SDK: 1.44.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Action["CreateGroup"] = "createGroup"; + /** + * The **Ungroup Liveboard Group** menu action on a Liveboard. + * Allows ungrouping a liveboard group. + * @example + * ```js + * disabledActions: [Action.UngroupLiveboardGroup] + * ``` + * @version SDK: 1.44.0 | ThoughtSpot Cloud: 26.2.0.cl + */ + Action["UngroupLiveboardGroup"] = "ungroupLiveboardGroup"; + /** + * Controls visibility of the sidebar header (title and toggle button) + * in the Spotter past conversations sidebar. + * @example + * ```js + * hiddenActions: [Action.SpotterSidebarHeader] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterSidebarHeader"] = "spotterSidebarHeader"; + /** + * Controls visibility of the sidebar footer (documentation link) + * in the Spotter past conversations sidebar. + * @example + * ```js + * hiddenActions: [Action.SpotterSidebarFooter] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterSidebarFooter"] = "spotterSidebarFooter"; + /** + * Controls visibility and disable state of the sidebar toggle/expand button + * in the Spotter past conversations sidebar. + * @example + * ```js + * disabledActions: [Action.SpotterSidebarToggle] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterSidebarToggle"] = "spotterSidebarToggle"; + /** + * Controls visibility and disable state of the "New Chat" button + * in the Spotter past conversations sidebar. + * @example + * ```js + * disabledActions: [Action.SpotterNewChat] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterNewChat"] = "spotterNewChat"; + /** + * Controls visibility of the past conversation banner alert + * in the Spotter interface. + * @example + * ```js + * hiddenActions: [Action.SpotterPastChatBanner] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterPastChatBanner"] = "spotterPastChatBanner"; + /** + * Controls visibility and disable state of the conversation edit menu + * (three-dot menu) in the Spotter past conversations sidebar. + * @example + * ```js + * disabledActions: [Action.SpotterChatMenu] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterChatMenu"] = "spotterChatMenu"; + /** + * Controls visibility and disable state of the rename action + * in the Spotter conversation edit menu. + * @example + * ```js + * disabledActions: [Action.SpotterChatRename] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterChatRename"] = "spotterChatRename"; + /** + * Controls visibility and disable state of the delete action + * in the Spotter conversation edit menu. + * @example + * ```js + * disabledActions: [Action.SpotterChatDelete] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterChatDelete"] = "spotterChatDelete"; + /** + * Controls visibility and disable state of the documentation/best practices + * link in the Spotter sidebar footer. + * @example + * ```js + * disabledActions: [Action.SpotterDocs] + * ``` + * @version SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl + */ + Action["SpotterDocs"] = "spotterDocs"; + /** + * Controls visibility and disable state of the connector resources + * section in the Spotter chat interface. + * @example + * ```js + * hiddenActions: [Action.SpotterChatConnectorResources] + * disabledActions: [Action.SpotterChatConnectorResources] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot Cloud: 26.5.0.cl + */ + Action["SpotterChatConnectorResources"] = "spotterChatConnectorResources"; + /** + * Controls visibility and disable state of the connectors + * in the Spotter chat interface. + * @example + * ```js + * hiddenActions: [Action.SpotterChatConnectors] + * disabledActions: [Action.SpotterChatConnectors] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot Cloud: 26.5.0.cl + */ + Action["SpotterChatConnectors"] = "spotterChatConnectors"; + /** + * Controls visibility and disable state of the mode switcher + * in the Spotter chat interface. + * @example + * ```js + * hiddenActions: [Action.SpotterChatModeSwitcher] + * disabledActions: [Action.SpotterChatModeSwitcher] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot Cloud: 26.5.0.cl + */ + Action["SpotterChatModeSwitcher"] = "spotterChatModeSwitcher"; + /** + * The **Include current period** checkbox for date filters. + * Controls the visibility and availability of the option to include + * the current time period in filter results. + * @example + * ```js + * hiddenActions: [Action.IncludeCurrentPeriod] + * disabledActions: [Action.IncludeCurrentPeriod] + * ``` + * @version SDK: 1.45.0 | ThoughtSpot: 26.4.0.cl + */ + Action["IncludeCurrentPeriod"] = "includeCurrentPeriod"; + /** + * The **Send Test Email** button in the Liveboard schedule modal. + * Allows sending a test schedule email to self or all recipients. + * Requires `isSendNowLiveboardSchedulingEnabled` to be enabled. + * @example + * ```js + * disabledActions: [Action.SendTestScheduleEmail] + * hiddenActions: [Action.SendTestScheduleEmail] + * ``` + * @version SDK: 1.48.0 | ThoughtSpot Cloud: 26.5.0.cl + */ + Action["SendTestScheduleEmail"] = "sendTestScheduleEmail"; + /** + * The thumbs up/down feedback buttons in the SpotterViz panel. + * Visible by default. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * hiddenActions: [Action.SpotterVizFeedback] + * disabledActions: [Action.SpotterVizFeedback] + * ``` + */ + Action["SpotterVizFeedback"] = "spotterVizFeedback"; + /** + * The version restore button on checkpoint cards in the SpotterViz panel. + * Visible by default. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * hiddenActions: [Action.SpotterVizCheckpointRestore] + * disabledActions: [Action.SpotterVizCheckpointRestore] + * ``` + */ + Action["SpotterVizCheckpointRestore"] = "spotterVizCheckpointRestore"; + /** + * The **SpotterViz** button in the top edit header. + * Visible by default. + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + * @example + * ```js + * hiddenActions: [Action.SpotterViz] + * disabledActions: [Action.SpotterViz] + * ``` + */ + Action["SpotterViz"] = "spotterViz"; + /** + * Clears browser cache and fetches new data for liveboard ChartViz Containers. + * Requires `enableLiveboardDataCache` to be enabled. + * @example + * ```js + * disabledActions: [Action.RefreshLiveboardBrowserCache] + * hiddenActions: [Action.RefreshLiveboardBrowserCache] + * ``` + * @version SDK: 1.49.0 | ThoughtSpot Cloud: 26.6.0.cl + */ + Action["RefreshLiveboardBrowserCache"] = "refreshLiveboardBrowserCache"; +})(Action || (Action = {})); +var PrefetchFeatures; +(function (PrefetchFeatures) { + PrefetchFeatures["FullApp"] = "FullApp"; + PrefetchFeatures["SearchEmbed"] = "SearchEmbed"; + PrefetchFeatures["LiveboardEmbed"] = "LiveboardEmbed"; + PrefetchFeatures["VizEmbed"] = "VizEmbed"; +})(PrefetchFeatures || (PrefetchFeatures = {})); +/** + * Enum for options to change context trigger. + * The `BOTH_CLICKS` option is available from 10.8.0.cl. + */ +var ContextMenuTriggerOptions; +(function (ContextMenuTriggerOptions) { + ContextMenuTriggerOptions["LEFT_CLICK"] = "left-click"; + ContextMenuTriggerOptions["RIGHT_CLICK"] = "right-click"; + ContextMenuTriggerOptions["BOTH_CLICKS"] = "both-clicks"; +})(ContextMenuTriggerOptions || (ContextMenuTriggerOptions = {})); +/** + * Enum options to show custom actions at different + * positions in the application. + */ +var CustomActionsPosition; +(function (CustomActionsPosition) { + /** + * Shows the action as a primary button + * in the toolbar area of the embed. + */ + CustomActionsPosition["PRIMARY"] = "PRIMARY"; + /** + * Shows the action inside the "More" menu + * (three-dot overflow menu). + */ + CustomActionsPosition["MENU"] = "MENU"; + /** + * Shows the action in the right-click + * context menu. Only supported for + * {@link CustomActionTarget.VIZ}, + * {@link CustomActionTarget.ANSWER}, and + * {@link CustomActionTarget.SPOTTER} targets. + */ + CustomActionsPosition["CONTEXTMENU"] = "CONTEXTMENU"; +})(CustomActionsPosition || (CustomActionsPosition = {})); +/** + * Enum options to mention the target of the code-based custom action. + * The target determines which type of ThoughtSpot object the action is + * associated with, and also controls which positions and scoping options + * are available. + */ +var CustomActionTarget; +(function (CustomActionTarget) { + /** + * Action applies at the Liveboard level. + * Supported positions: + * {@link CustomActionsPosition.PRIMARY}, + * {@link CustomActionsPosition.MENU}. + * Can be scoped with + * `metadataIds.liveboardIds`, + * `orgIds`, and `groupIds`. + */ + CustomActionTarget["LIVEBOARD"] = "LIVEBOARD"; + /** + * Action applies to individual + * visualizations (charts/tables). + * Supported positions: + * {@link CustomActionsPosition.PRIMARY}, + * {@link CustomActionsPosition.MENU}, + * {@link CustomActionsPosition.CONTEXTMENU}. + * Can be scoped with `metadataIds` + * (answerIds, liveboardIds, vizIds), + * `dataModelIds` (modelIds, + * modelColumnNames), `orgIds`, + * and `groupIds`. + */ + CustomActionTarget["VIZ"] = "VIZ"; + /** + * Action applies to saved or unsaved + * Answers. Supported positions: + * {@link CustomActionsPosition.PRIMARY}, + * {@link CustomActionsPosition.MENU}, + * {@link CustomActionsPosition.CONTEXTMENU}. + * Can be scoped with + * `metadataIds.answerIds`, + * `dataModelIds` (modelIds, + * modelColumnNames), `orgIds`, + * and `groupIds`. + */ + CustomActionTarget["ANSWER"] = "ANSWER"; + /** + * Action applies to Spotter + * (AI-powered search). + * Supported positions: + * {@link CustomActionsPosition.MENU}, + * {@link CustomActionsPosition.CONTEXTMENU}. + * Can be scoped with + * `dataModelIds.modelIds`, + * `orgIds`, and `groupIds`. + */ + CustomActionTarget["SPOTTER"] = "SPOTTER"; +})(CustomActionTarget || (CustomActionTarget = {})); +/** + * Enum options to show or suppress Visual Embed SDK and + * ThoughtSpot application logs in the console output. + * This attribute doesn't support suppressing + * browser warnings or errors. + */ +var LogLevel; +(function (LogLevel) { + /** + * No application or SDK-related logs will be logged + * in the console output. + * @example + * ```js + * init({ + * ... //other embed view config, + * logLevel: LogLevel.SILENT, + * }) + * ``` + * @version SDK: 1.26.7 | ThoughtSpot Cloud: 9.10.0.cl + */ + LogLevel["SILENT"] = "SILENT"; + /** + * Log only errors in the console output. + * @example + * ```js + * init({ + * ... //other embed view config, + * logLevel: LogLevel.ERROR, + * }) + * ``` + * @version SDK: 1.26.7 | ThoughtSpot Cloud: 9.10.0.cl + */ + LogLevel["ERROR"] = "ERROR"; + /** + * Log only warnings and errors in the console output. + * @example + * ```js + * init({ + * ... //other embed view config, + * logLevel: LogLevel.WARN, + * }) + * ``` + * @version SDK: 1.26.7 | ThoughtSpot Cloud: 9.10.0.cl + */ + LogLevel["WARN"] = "WARN"; + /** + * Log only the information alerts, warnings, and errors + * in the console output. + * @example + * ```js + * init({ + * ... //other embed view config, + * logLevel: LogLevel.INFO, + * }) + * ``` + * @version SDK: 1.26.7 | ThoughtSpot Cloud: 9.10.0.cl + */ + LogLevel["INFO"] = "INFO"; + /** + * Log debug messages, warnings, information alerts, + * and errors in the console output. + * @example + * ```js + * init({ + * ... //other embed view config, + * logLevel: LogLevel.DEBUG, + * }) + * ``` + * @version SDK: 1.26.7 | ThoughtSpot Cloud: 9.10.0.cl + */ + LogLevel["DEBUG"] = "DEBUG"; + /** + * All logs will be logged in the browser console. + * @example + * ```js + * init({ + * ... //other embed view config, + * logLevel: LogLevel.TRACE, + * }) + * ``` + * @version SDK: 1.26.7 | ThoughtSpot Cloud: 9.10.0.cl + */ + LogLevel["TRACE"] = "TRACE"; +})(LogLevel || (LogLevel = {})); +/** + * Error types emitted by embedded components. + * + * These enum values categorize different types of errors that can occur during + * the lifecycle of an embedded ThoughtSpot component. + * Use {@link EmbedErrorDetailsEvent} and {@link EmbedErrorCodes} to handle specific errors. + * @version SDK: 1.44.2 | ThoughtSpot: 26.2.0.cl + * @group Error Handling + * + * @example + * Handle specific error types + * ```js + * embed.on(EmbedEvent.Error, (error) => { + * switch (error.errorType) { + * case ErrorDetailsTypes.API: + * console.error('API error:', error.message); + * break; + * case ErrorDetailsTypes.VALIDATION_ERROR: + * console.error('Validation error:', error.message); + * break; + * case ErrorDetailsTypes.NETWORK: + * console.error('Network error:', error.message); + * break; + * default: + * console.error('Unknown error:', error); + * } + * }); + * ``` + */ +var ErrorDetailsTypes; +(function (ErrorDetailsTypes) { + /** API call failure */ + ErrorDetailsTypes["API"] = "API"; + /** General validation error */ + ErrorDetailsTypes["VALIDATION_ERROR"] = "VALIDATION_ERROR"; + /** Network connectivity or request error */ + ErrorDetailsTypes["NETWORK"] = "NETWORK"; +})(ErrorDetailsTypes || (ErrorDetailsTypes = {})); +/** + * Error codes for identifying specific issues in embedded ThoughtSpot components. Use + * {@link EmbedErrorDetailsEvent} and {@link ErrorDetailsTypes} codes for precise error + * handling and debugging. + * + * @version SDK: 1.44.2 | ThoughtSpot: 26.2.0.cl + * @group Error Handling + + * @example + * Handle specific error codes in the error event handler + * ```js + * embed.on(EmbedEvent.Error, (error) => { + * switch (error.code) { + * case EmbedErrorCodes.WORKSHEET_ID_NOT_FOUND: + * console.error('Worksheet ID not found:', error.message); + * break; + * case EmbedErrorCodes.LIVEBOARD_ID_MISSING: + * console.error('Liveboard ID is missing:', error.message); + * break; + * case EmbedErrorCodes.CONFLICTING_ACTIONS_CONFIG: + * console.error('Conflicting actions configuration:', error.message); + * break; + * case EmbedErrorCodes.CONFLICTING_TABS_CONFIG: + * console.error('Conflicting tabs configuration:', error.message); + * break; + * default: + * console.error('Unknown error:', error); + * } + * }); + * ``` + * */ +var EmbedErrorCodes; +(function (EmbedErrorCodes) { + /** Worksheet ID not found or does not exist */ + EmbedErrorCodes["WORKSHEET_ID_NOT_FOUND"] = "WORKSHEET_ID_NOT_FOUND"; + /** Required Liveboard ID is missing from configuration */ + EmbedErrorCodes["LIVEBOARD_ID_MISSING"] = "LIVEBOARD_ID_MISSING"; + /** Conflicting action configuration detected */ + EmbedErrorCodes["CONFLICTING_ACTIONS_CONFIG"] = "CONFLICTING_ACTIONS_CONFIG"; + /** Conflicting tab configuration detected */ + EmbedErrorCodes["CONFLICTING_TABS_CONFIG"] = "CONFLICTING_TABS_CONFIG"; + /** Error during component initialization */ + EmbedErrorCodes["INIT_ERROR"] = "INIT_ERROR"; + /** Network connectivity or request error */ + EmbedErrorCodes["NETWORK_ERROR"] = "NETWORK_ERROR"; + /** Custom action validation failed */ + EmbedErrorCodes["CUSTOM_ACTION_VALIDATION"] = "CUSTOM_ACTION_VALIDATION"; + /** Authentication/login failed */ + EmbedErrorCodes["LOGIN_FAILED"] = "LOGIN_FAILED"; + /** Render method was not called before attempting to use the component */ + EmbedErrorCodes["RENDER_NOT_CALLED"] = "RENDER_NOT_CALLED"; + /** Host event type is undefined or invalid */ + EmbedErrorCodes["HOST_EVENT_TYPE_UNDEFINED"] = "HOST_EVENT_TYPE_UNDEFINED"; + /** Error parsing api intercept body */ + EmbedErrorCodes["PARSING_API_INTERCEPT_BODY_ERROR"] = "PARSING_API_INTERCEPT_BODY_ERROR"; + /** Failed to update embed parameters during pre-render */ + EmbedErrorCodes["UPDATE_PARAMS_FAILED"] = "UPDATE_PARAMS_FAILED"; + /** Invalid URL provided in configuration */ + EmbedErrorCodes["INVALID_URL"] = "INVALID_URL"; + /** Host event payload validation failed */ + EmbedErrorCodes["HOST_EVENT_VALIDATION"] = "HOST_EVENT_VALIDATION"; + /** UpdateFilters payload is invalid - missing or malformed filter/filters */ + EmbedErrorCodes["UPDATEFILTERS_INVALID_PAYLOAD"] = "UPDATEFILTERS_INVALID_PAYLOAD"; + /** DrillDown payload is invalid - missing or malformed points */ + EmbedErrorCodes["DRILLDOWN_INVALID_PAYLOAD"] = "DRILLDOWN_INVALID_PAYLOAD"; +})(EmbedErrorCodes || (EmbedErrorCodes = {})); +/** + * Context types for specifying the page context when triggering host events. + * Used as the third parameter in the `trigger` method to help ThoughtSpot + * understand the current page context for better event handling. + * + * @example + * ```js + * import { HostEvent, ContextType } from '@thoughtspot/visual-embed-sdk'; + * + * // Trigger an event with specific context + * embed.trigger(HostEvent.Pin, { vizId: "123", liveboardId: "456" }, ContextType.Search); + * ``` + * + * @version SDK: 1.45.2 | ThoughtSpot: 26.3.0.cl + * @group Events + */ +var ContextType; +(function (ContextType) { + /** + * Search answer context for search page or edit viz dialog on liveboard page. + */ + ContextType["Search"] = "search-answer"; + /** + * Liveboard context for liveboard page. + */ + ContextType["Liveboard"] = "liveboard"; + /** + * Answer context for explore modal/page on liveboard page. + */ + ContextType["Answer"] = "answer"; + /** + * Spotter context for spotter modal/page. + */ + ContextType["Spotter"] = "spotter"; + /** + * Other context for any other generic page. + */ + ContextType["Other"] = "other"; +})(ContextType || (ContextType = {})); +/** + * Enum for the type of API intercepted + */ +var InterceptedApiType; +(function (InterceptedApiType) { + /** + * The apis that are use to get the data for the embed + */ + InterceptedApiType["AnswerData"] = "AnswerData"; + /** + * This will intercept all the apis + */ + InterceptedApiType["ALL"] = "ALL"; + /** + * The apis that are use to get the data for the liveboard + */ + InterceptedApiType["LiveboardData"] = "LiveboardData"; +})(InterceptedApiType || (InterceptedApiType = {})); +/** + * Data label filter operators + * @group Visual Overrides + */ +var DataLabelFilterOperator; +(function (DataLabelFilterOperator) { + /** Greater than */ + DataLabelFilterOperator["GreaterThan"] = "GREATER_THAN"; + /** Less than */ + DataLabelFilterOperator["LessThan"] = "LESS_THAN"; + /** Greater than or equal to */ + DataLabelFilterOperator["GreaterThanOrEqualTo"] = "GREATER_THAN_OR_EQUAL_TO"; + /** Less than or equal to */ + DataLabelFilterOperator["LessThanOrEqualTo"] = "LESS_THAN_OR_EQUAL_TO"; + /** Equal to */ + DataLabelFilterOperator["EqualTo"] = "EQUAL_TO"; + /** Not equal to */ + DataLabelFilterOperator["NotEqualTo"] = "NOT_EQUAL_TO"; +})(DataLabelFilterOperator || (DataLabelFilterOperator = {})); +/** + * Conditional formatting operators + * @group Visual Overrides + */ +var ConditionalFormattingOperator; +(function (ConditionalFormattingOperator) { + /** Is equal to */ + ConditionalFormattingOperator["Is"] = "IS"; + /** Is not equal to */ + ConditionalFormattingOperator["IsNot"] = "IS_NOT"; + /** Contains */ + ConditionalFormattingOperator["Contains"] = "CONTAINS"; + /** Does not contain */ + ConditionalFormattingOperator["DoesNotContain"] = "DOES_NOT_CONTAIN"; + /** Starts with */ + ConditionalFormattingOperator["StartsWith"] = "STARTS_WITH"; + /** Ends with */ + ConditionalFormattingOperator["EndsWith"] = "ENDS_WITH"; + /** Greater than */ + ConditionalFormattingOperator["GreaterThan"] = "GREATER_THAN"; + /** Less than */ + ConditionalFormattingOperator["LessThan"] = "LESS_THAN"; + /** Greater than or equal to */ + ConditionalFormattingOperator["GreaterThanEqualTo"] = "GREATER_THAN_EQUAL_TO"; + /** Less than or equal to */ + ConditionalFormattingOperator["LessThanEqualTo"] = "LESS_THAN_EQUAL_TO"; + /** Equal to */ + ConditionalFormattingOperator["EqualTo"] = "EQUAL_TO"; + /** Not equal to */ + ConditionalFormattingOperator["NotEqualTo"] = "NOT_EQUAL_TO"; + /** Is between */ + ConditionalFormattingOperator["IsBetween"] = "IS_BETWEEN"; + /** Is null */ + ConditionalFormattingOperator["IsNull"] = "IS_NULL"; + /** Is not null */ + ConditionalFormattingOperator["IsNotNull"] = "IS_NOT_NULL"; +})(ConditionalFormattingOperator || (ConditionalFormattingOperator = {})); +/** + * Background format types for conditional formatting + * @group Visual Overrides + */ +var BackgroundFormatType; +(function (BackgroundFormatType) { + /** Solid color background */ + BackgroundFormatType["Solid"] = "SOLID"; + /** Gradient background */ + BackgroundFormatType["Gradient"] = "GRADIENT"; +})(BackgroundFormatType || (BackgroundFormatType = {})); +/** + * Comparison types for conditional formatting + * @group Visual Overrides + */ +var ConditionalFormattingComparisonType; +(function (ConditionalFormattingComparisonType) { + /** Value-based comparison */ + ConditionalFormattingComparisonType["ValueBased"] = "VALUE_BASED"; + /** Column-based comparison */ + ConditionalFormattingComparisonType["ColumnBased"] = "COLUMN_BASED"; + /** Parameter-based comparison */ + ConditionalFormattingComparisonType["ParameterBased"] = "PARAMETER_BASED"; +})(ConditionalFormattingComparisonType || (ConditionalFormattingComparisonType = {})); +/** + * Legend position options + * @group Visual Overrides + */ +var LegendPosition; +(function (LegendPosition) { + /** Position legend at the top */ + LegendPosition["Top"] = "top"; + /** Position legend at the bottom */ + LegendPosition["Bottom"] = "bottom"; + /** Position legend on the left */ + LegendPosition["Left"] = "left"; + /** Position legend on the right */ + LegendPosition["Right"] = "right"; +})(LegendPosition || (LegendPosition = {})); +/** + * Table theme options + * @group Visual Overrides + */ +var TableTheme; +(function (TableTheme) { + /** Outline theme */ + TableTheme["Outline"] = "OUTLINE"; + /** Row theme */ + TableTheme["Row"] = "ROW"; + /** Zebra theme */ + TableTheme["Zebra"] = "ZEBRA"; +})(TableTheme || (TableTheme = {})); +/** + * Table content density options + * @group Visual Overrides + */ +var TableContentDensity; +(function (TableContentDensity) { + /** Regular density */ + TableContentDensity["Regular"] = "REGULAR"; + /** Compact density */ + TableContentDensity["Compact"] = "COMPACT"; +})(TableContentDensity || (TableContentDensity = {})); + +var version="1.48.0";var packageInfo = {version:version}; + +const logFunctions = { + [LogLevel.SILENT]: () => undefined, + [LogLevel.ERROR]: console.error, + [LogLevel.WARN]: console.warn, + [LogLevel.INFO]: console.info, + [LogLevel.DEBUG]: console.debug, + [LogLevel.TRACE]: console.trace, +}; +let globalLogLevelOverride = LogLevel.TRACE; +const setGlobalLogLevelOverride = (logLevel) => { + globalLogLevelOverride = logLevel; +}; +const logLevelToNumber = { + [LogLevel.SILENT]: 0, + [LogLevel.ERROR]: 1, + [LogLevel.WARN]: 2, + [LogLevel.INFO]: 3, + [LogLevel.DEBUG]: 4, + [LogLevel.TRACE]: 5, +}; +const compareLogLevels = (logLevel1, logLevel2) => { + const logLevel1Index = logLevelToNumber[logLevel1]; + const logLevel2Index = logLevelToNumber[logLevel2]; + return logLevel1Index - logLevel2Index; +}; +class Logger { + constructor() { + this.logLevel = LogLevel.ERROR; + this.setLogLevel = (newLogLevel) => { + this.logLevel = newLogLevel; + }; + this.getLogLevel = () => this.logLevel; + } + canLog(logLevel) { + if (logLevel === LogLevel.SILENT) + return false; + if (globalLogLevelOverride !== undefined) { + return compareLogLevels(globalLogLevelOverride, logLevel) >= 0; + } + return compareLogLevels(this.logLevel, logLevel) >= 0; + } + logMessages(args, logLevel) { + if (this.canLog(logLevel)) { + const logFn = logFunctions[logLevel]; + if (logFn) { + logFn(`[vesdk-${version}]`, ...args); + } + } + } + log(...args) { + this.info(args); + } + info(...args) { + this.logMessages(args, LogLevel.INFO); + } + debug(...args) { + this.logMessages(args, LogLevel.DEBUG); + } + trace(...args) { + this.logMessages(args, LogLevel.TRACE); + } + error(...args) { + this.logMessages(args, LogLevel.ERROR); + } + warn(...args) { + this.logMessages(args, LogLevel.WARN); + } +} +const logger$3 = new Logger(); + +// istanbul ignore next +const isObject$1 = (obj) => { + if (typeof obj === "object" && obj !== null) { + if (typeof Object.getPrototypeOf === "function") { + const prototype = Object.getPrototypeOf(obj); + return prototype === Object.prototype || prototype === null; + } + return Object.prototype.toString.call(obj) === "[object Object]"; + } + return false; +}; +const merge = (...objects) => objects.reduce((result, current) => { + if (Array.isArray(current)) { + throw new TypeError("Arguments provided to ts-deepmerge must be objects, not arrays."); + } + Object.keys(current).forEach((key) => { + if (["__proto__", "constructor", "prototype"].includes(key)) { + return; + } + if (Array.isArray(result[key]) && Array.isArray(current[key])) { + result[key] = merge.options.mergeArrays + ? merge.options.uniqueArrayItems + ? Array.from(new Set(result[key].concat(current[key]))) + : [...result[key], ...current[key]] + : current[key]; + } + else if (isObject$1(result[key]) && isObject$1(current[key])) { + result[key] = merge(result[key], current[key]); + } + else { + result[key] = + current[key] === undefined + ? merge.options.allowUndefinedOverrides + ? current[key] + : result[key] + : current[key]; + } + }); + return result; +}, {}); +const defaultOptions = { + allowUndefinedOverrides: true, + mergeArrays: true, + uniqueArrayItems: true, +}; +merge.options = defaultOptions; +merge.withOptions = (options, ...objects) => { + merge.options = Object.assign(Object.assign({}, defaultOptions), options); + const result = merge(...objects); + merge.options = defaultOptions; + return result; +}; + +const ERROR_MESSAGE = { + INVALID_THOUGHTSPOT_HOST: 'Error parsing ThoughtSpot host. Please provide a valid URL.', + SPOTTER_EMBED_WORKSHEED_ID_NOT_FOUND: 'Please select a Model to get started', + LIVEBOARD_VIZ_ID_VALIDATION: 'Please select a Liveboard to embed.', + TRIGGER_TIMED_OUT: 'Trigger timed-out in getting a response', + SEARCHEMBED_BETA_WRANING_MESSAGE: 'SearchEmbed is in Beta in this release.', + THIRD_PARTY_COOKIE_BLOCKED_ALERT: 'Third-party cookie access is blocked on this browser. Please allow third-party cookies for this to work properly. \nYou can use `suppressNoCookieAccessAlert` to suppress this message.', + DUPLICATE_TOKEN_ERR: 'Duplicate token. Please issue a new token every time getAuthToken callback is called. See https://developers.thoughtspot.com/docs/?pageid=embed-auth#trusted-auth-embed for more details.', + SDK_NOT_INITIALIZED: 'SDK not initialized', + SESSION_INFO_FAILED: 'Failed to get session information', + INVALID_TOKEN_ERROR: 'Received invalid token from getAuthToken callback or authToken endpoint.', + INVALID_TOKEN_TYPE_ERROR: 'Expected getAuthToken to return a string, but received a {invalidType}.', + MIXPANEL_TOKEN_NOT_FOUND: 'Mixpanel token not found in session info', + PRERENDER_ID_MISSING: 'PreRender ID is required for preRender', + SYNC_STYLE_CALLED_BEFORE_RENDER: 'PreRender should be called before using syncPreRenderStyle', + CSP_VIOLATION_ALERT: 'CSP violation detected. Please check the console errors for more details.', + CSP_FRAME_HOST_VIOLATION_LOG_MESSAGE: 'Please set up CSP correctly for the application to start working. For more information, see https://developers.thoughtspot.com/docs/security-settings#csp-viz-embed-hosts. \n If the issue persists, refer to https://developers.thoughtspot.com/docs/security-settings#csp-viz-embed-hosts', + MISSING_REPORTING_OBSERVER: 'ReportingObserver not supported', + RENDER_CALLED_BEFORE_INIT: 'Looks like render was called before calling init, the render won\'t start until init is called.\nFor more info check\n1. https://developers.thoughtspot.com/docs/Function_init#_init\n2.https://developers.thoughtspot.com/docs/getting-started#initSdk', + OFFLINE_WARNING: 'Network not Detected. Embed is offline. Please reconnect and refresh', + INIT_SDK_REQUIRED: 'You need to init the ThoughtSpot SDK module first', + CONFLICTING_ACTIONS_CONFIG: 'You cannot have both hidden actions and visible actions', + CONFLICTING_TABS_CONFIG: 'You cannot have both hidden Tabs and visible Tabs', + RENDER_BEFORE_EVENTS_REQUIRED: 'Please call render before triggering events', + HOST_EVENT_TYPE_UNDEFINED: 'Host event type is undefined', + LOGIN_FAILED: 'Login failed', + ERROR_PARSING_API_INTERCEPT_BODY: 'Error parsing api intercept body', + SSR_ENVIRONMENT_ERROR: 'SSR environment detected. This function cannot be called in SSR environment.', + UPDATE_PARAMS_FAILED: 'Failed to update embed parameters', + INVALID_SPOTTER_DOCUMENTATION_URL: 'Invalid spotterDocumentationUrl. Please provide a valid http or https URL.', + UPDATEFILTERS_INVALID_PAYLOAD: 'UpdateFilters requires a valid filter or filters array. Expected: { filter: { column, oper, values } } or { filters: [{ column, oper, values }, ...] }', + DRILLDOWN_INVALID_PAYLOAD: 'DrillDown requires a valid points object. Expected: { points: { clickedPoint?, selectedPoints? }, autoDrillDown?, vizId? }', +}; +const CUSTOM_ACTIONS_ERROR_MESSAGE = { + INVALID_ACTION_OBJECT: 'Custom Action Validation Error: Invalid action object provided', + MISSING_REQUIRED_FIELDS: (id, missingFields) => `Custom Action Validation Error for '${id}': Missing required fields: ${missingFields.join(', ')}`, + UNSUPPORTED_TARGET: (id, targetType) => `Custom Action Validation Error for '${id}': Target type '${targetType}' is not supported`, + INVALID_POSITION: (position, targetType, supportedPositions) => `Position '${position}' is not supported for ${targetType.toLowerCase()}-level custom actions. Supported positions: ${supportedPositions}`, + INVALID_METADATA_IDS: (targetType, invalidIds, supportedIds) => `Invalid metadata IDs for ${targetType.toLowerCase()}-level custom actions: ${invalidIds.join(', ')}. Supported metadata IDs: ${supportedIds}`, + INVALID_DATA_MODEL_IDS: (targetType, invalidIds, supportedIds) => `Invalid data model IDs for ${targetType.toLowerCase()}-level custom actions: ${invalidIds.join(', ')}. Supported data model IDs: ${supportedIds}`, + INVALID_FIELDS: (targetType, invalidFields, supportedFields) => `Invalid fields for ${targetType.toLowerCase()}-level custom actions: ${invalidFields.join(', ')}. Supported fields: ${supportedFields}`, + DUPLICATE_IDS: (id, duplicateNames, keptName) => `Duplicate custom action ID '${id}' found. Actions with names '${duplicateNames.join("', '")}' will be ignored. Keeping '${keptName}'.`, +}; + +/** + * Copyright (c) 2023 + * + * Common utility functions for ThoughtSpot Visual Embed SDK + * @summary Utils + * @author Ayon Ghosh + */ +/** + * Construct a runtime filters query string from the given filters. + * Refer to the following docs for more details on runtime filter syntax: + * https://cloud-docs.thoughtspot.com/admin/ts-cloud/apply-runtime-filter.html + * https://cloud-docs.thoughtspot.com/admin/ts-cloud/runtime-filter-operators.html + * @param runtimeFilters + */ +const getFilterQuery = (runtimeFilters) => { + if (runtimeFilters && runtimeFilters.length) { + const filters = runtimeFilters.map((filter, valueIndex) => { + const index = valueIndex + 1; + const filterExpr = []; + filterExpr.push(`col${index}=${encodeURIComponent(filter.columnName)}`); + filterExpr.push(`op${index}=${filter.operator}`); + filterExpr.push(filter.values.map((value) => { + const encodedValue = typeof value === 'bigint' ? value.toString() : value; + return `val${index}=${encodeURIComponent(String(encodedValue))}`; + }).join('&')); + return filterExpr.join('&'); + }); + return `${filters.join('&')}`; + } + return null; +}; +/** + * Construct a runtime parameter override query string from the given option. + * @param runtimeParameters + */ +const getRuntimeParameters = (runtimeParameters) => { + if (runtimeParameters && runtimeParameters.length) { + const params = runtimeParameters.map((param, valueIndex) => { + const index = valueIndex + 1; + const filterExpr = []; + filterExpr.push(`param${index}=${encodeURIComponent(param.name)}`); + filterExpr.push(`paramVal${index}=${encodeURIComponent(param.value)}`); + return filterExpr.join('&'); + }); + return `${params.join('&')}`; + } + return null; +}; +/** + * Convert a value to a string representation to be sent as a query + * parameter to the ThoughtSpot app. + * @param value Any parameter value + */ +const serializeParam = (value) => { + // do not serialize primitive types + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + return value; + } + return JSON.stringify(value); +}; +/** + * Convert a value to a string: + * in case of an array, we convert it to CSV. + * in case of any other type, we directly return the value. + * @param value + */ +const paramToString = (value) => (Array.isArray(value) ? value.join(',') : value); +/** + * Return a query param string composed from the given params object + * @param queryParams + * @param shouldSerializeParamValues + */ +const getQueryParamString = (queryParams, shouldSerializeParamValues = false) => { + const qp = []; + const params = Object.keys(queryParams); + params.forEach((key) => { + const val = queryParams[key]; + if (val !== undefined) { + const serializedValue = shouldSerializeParamValues + ? serializeParam(val) + : paramToString(val); + qp.push(`${key}=${serializedValue}`); + } + }); + if (qp.length) { + return qp.join('&'); + } + return null; +}; +/** + * Get a string representation of a dimension value in CSS + * If numeric, it is considered in pixels. + * @param value + */ +const getCssDimension = (value) => { + if (typeof value === 'number') { + return `${value}px`; + } + return value; +}; +/** + * Validates if a string is a valid CSS margin value. + * @param value - The string to validate + * @returns true if the value is a valid CSS margin value, false otherwise + */ +const isValidCssMargin = (value) => { + if (isUndefined(value)) { + return false; + } + if (typeof value !== 'string') { + logger$3.error('Please provide a valid lazyLoadingMargin value (e.g., "10px")'); + return false; + } + // This pattern allows for an optional negative sign, and numbers + // that can be integers or decimals (including leading dot). + const cssUnitPattern = /^-?(\d+(\.\d*)?|\.\d+)(px|em|rem|%|vh|vw)$/i; + const parts = value.trim().split(/\s+/); + if (parts.length > 4) { + logger$3.error('Please provide a valid lazyLoadingMargin value (e.g., "10px")'); + return false; + } + const isValid = parts.every(part => { + const trimmedPart = part.trim(); + return trimmedPart.toLowerCase() === 'auto' || trimmedPart === '0' || cssUnitPattern.test(trimmedPart); + }); + if (!isValid) { + logger$3.error('Please provide a valid lazyLoadingMargin value (e.g., "10px")'); + return false; + } + return true; +}; +const getSSOMarker = (markerId) => { + const encStringToAppend = encodeURIComponent(markerId); + return `tsSSOMarker=${encStringToAppend}`; +}; +/** + * Append a string to a URL's hash fragment + * @param url A URL + * @param stringToAppend The string to append to the URL hash + */ +const appendToUrlHash = (url, stringToAppend) => { + let outputUrl = url; + const encStringToAppend = encodeURIComponent(stringToAppend); + const marker = `tsSSOMarker=${encStringToAppend}`; + let splitAdder = ''; + if (url.indexOf('#') >= 0) { + // If second half of hash contains a '?' already add a '&' instead of + // '?' which appends to query params. + splitAdder = url.split('#')[1].indexOf('?') >= 0 ? '&' : '?'; + } + else { + splitAdder = '#?'; + } + outputUrl = `${outputUrl}${splitAdder}${marker}`; + return outputUrl; +}; +/** + * + * @param url + * @param stringToAppend + * @param path + */ +function getRedirectUrl(url, stringToAppend, path = '') { + const targetUrl = path ? new URL(path, window.location.origin).href : url; + return appendToUrlHash(targetUrl, stringToAppend); +} +const getEncodedQueryParamsString = (queryString) => { + if (!queryString) { + return queryString; + } + return btoa(queryString).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); +}; +const getOffsetTop = (element) => { + const rect = element.getBoundingClientRect(); + return rect.top + window.scrollY; +}; +const embedEventStatus = { + START: 'start', + END: 'end', +}; +const setAttributes = (element, attributes) => { + Object.keys(attributes).forEach((key) => { + element.setAttribute(key, attributes[key].toString()); + }); +}; +const isCloudRelease = (version) => version.endsWith('.cl'); +/* For Search Embed: ReleaseVersionInBeta */ +const checkReleaseVersionInBeta = (releaseVersion, suppressBetaWarning) => { + if (releaseVersion !== '' && !isCloudRelease(releaseVersion)) { + const splittedReleaseVersion = releaseVersion.split('.'); + const majorVersion = Number(splittedReleaseVersion[0]); + const isBetaVersion = majorVersion < 8; + return !suppressBetaWarning && isBetaVersion; + } + return false; +}; +const getCustomisations = (embedConfig, viewConfig) => { + const customizationsFromViewConfig = viewConfig.customizations; + const customizationsFromEmbedConfig = embedConfig.customizations + || embedConfig.customisations; + const customizations = { + style: { + ...customizationsFromEmbedConfig?.style, + ...customizationsFromViewConfig?.style, + customCSS: { + ...customizationsFromEmbedConfig?.style?.customCSS, + ...customizationsFromViewConfig?.style?.customCSS, + }, + customCSSUrl: customizationsFromViewConfig?.style?.customCSSUrl + || customizationsFromEmbedConfig?.style?.customCSSUrl, + }, + content: { + ...customizationsFromEmbedConfig?.content, + ...customizationsFromViewConfig?.content, + }, + }; + return customizations; +}; +const getRuntimeFilters = (runtimefilters) => getFilterQuery(runtimefilters || []); +/** + * Gets a reference to the DOM node given + * a selector. + * @param domSelector + */ +function getDOMNode(domSelector) { + return typeof domSelector === 'string' ? document.querySelector(domSelector) : domSelector; +} +const deepMerge = (target, source) => merge(target, source); +const getOperationNameFromQuery = (query) => { + const regex = /(?:query|mutation)\s+(\w+)/; + const matches = query.match(regex); + return matches?.[1]; +}; +/** + * + * @param obj + */ +function removeTypename(obj) { + if (!obj || typeof obj !== 'object') + return obj; + for (const key in obj) { + if (key === '__typename') { + delete obj[key]; + } + else if (typeof obj[key] === 'object') { + removeTypename(obj[key]); + } + } + return obj; +} +/** + * Sets the specified style properties on an HTML element. + * @param {HTMLElement} element - The HTML element to which the styles should be applied. + * @param {Partial} styleProperties - An object containing style + * property names and their values. + * @example + * // Apply styles to an element + * const element = document.getElementById('myElement'); + * const styles = { + * backgroundColor: 'red', + * fontSize: '16px', + * }; + * setStyleProperties(element, styles); + */ +const setStyleProperties = (element, styleProperties) => { + if (!element?.style) + return; + Object.keys(styleProperties).forEach((styleProperty) => { + const styleKey = styleProperty; + const value = styleProperties[styleKey]; + if (value !== undefined) { + element.style[styleKey] = value.toString(); + } + }); +}; +/** + * Removes specified style properties from an HTML element. + * @param {HTMLElement} element - The HTML element from which the styles should be removed. + * @param {string[]} styleProperties - An array of style property names to be removed. + * @example + * // Remove styles from an element + * const element = document.getElementById('myElement'); + * element.style.backgroundColor = 'red'; + * const propertiesToRemove = ['backgroundColor']; + * removeStyleProperties(element, propertiesToRemove); + */ +const removeStyleProperties = (element, styleProperties) => { + if (!element?.style) + return; + styleProperties.forEach((styleProperty) => { + element.style.removeProperty(styleProperty); + }); +}; +const isUndefined = (value) => value === undefined; +// Return if the value is a string, double or boolean. +const getTypeFromValue = (value) => { + if (typeof value === 'string') { + return ['char', 'string']; + } + if (typeof value === 'number') { + return ['double', 'double']; + } + if (typeof value === 'boolean') { + return ['boolean', 'boolean']; + } + return ['', '']; +}; +const sdkWindowKey = '_tsEmbedSDK'; +/** + * Stores a value in the global `window` object under the `_tsEmbedSDK` namespace. + * @param key - The key under which the value will be stored. + * @param value - The value to store. + * @param options - Additional options. + * @param options.ignoreIfAlreadyExists - Does not set if value for key is set. + * + * @returns The stored value. + * + * @version SDK: 1.36.2 | ThoughtSpot: * + */ +function storeValueInWindow(key, value, options = {}) { + if (isWindowUndefined()) + return value; + if (!window[sdkWindowKey]) { + window[sdkWindowKey] = {}; + } + if (options.ignoreIfAlreadyExists && key in window[sdkWindowKey]) { + return window[sdkWindowKey][key]; + } + window[sdkWindowKey][key] = value; + return value; +} +/** + * Retrieves a stored value from the global + * `window` object under the `_tsEmbedSDK` namespace. + * Returns undefined in SSR environment. + */ +const getValueFromWindow = (key) => { + if (isWindowUndefined()) + return undefined; + return window?.[sdkWindowKey]?.[key]; +}; +/** + * Check if an array includes a string value + * @param arr - The array to check + * @param key - The string to search for + * @returns boolean indicating if the string is found in the array + */ +const arrayIncludesString = (arr, key) => { + return arr.some(item => typeof item === 'string' && item === key); +}; +/** + * Check if the document is currently in fullscreen mode + */ +const isInFullscreen = () => { + return !!(document.fullscreenElement || + document.webkitFullscreenElement || + document.mozFullScreenElement || + document.msFullscreenElement); +}; +/** + * Handle Present HostEvent by entering fullscreen mode + * @param iframe The iframe element to make fullscreen + */ +const handlePresentEvent = async (iframe) => { + if (isInFullscreen()) { + return; // Already in fullscreen + } + // Browser-specific methods to enter fullscreen mode + const fullscreenMethods = [ + 'requestFullscreen', + 'webkitRequestFullscreen', + 'mozRequestFullScreen', + 'msRequestFullscreen' // IE/Edge + ]; + for (const method of fullscreenMethods) { + if (typeof iframe[method] === 'function') { + try { + const result = iframe[method](); + await Promise.resolve(result); + return; + } + catch (error) { + logger$3.warn(`Failed to enter fullscreen using ${method}:`, error); + } + } + } + logger$3.error('Fullscreen API is not supported by this browser.'); +}; +/** + * Handle ExitPresentMode EmbedEvent by exiting fullscreen mode + */ +const handleExitPresentMode = async () => { + if (!isInFullscreen()) { + return; // Not in fullscreen + } + const exitFullscreenMethods = [ + 'exitFullscreen', + 'webkitExitFullscreen', + 'mozCancelFullScreen', + 'msExitFullscreen' // IE/Edge + ]; + // Try each method until one works + for (const method of exitFullscreenMethods) { + if (typeof document[method] === 'function') { + try { + const result = document[method](); + await Promise.resolve(result); + return; + } + catch (error) { + logger$3.warn(`Failed to exit fullscreen using ${method}:`, error); + } + } + } + logger$3.warn('Exit fullscreen API is not supported by this browser.'); +}; +const calculateVisibleElementData = (element) => { + const rect = element.getBoundingClientRect(); + const windowHeight = window.innerHeight; + const windowWidth = window.innerWidth; + const frameRelativeTop = Math.max(rect.top, 0); + const frameRelativeLeft = Math.max(rect.left, 0); + const frameRelativeBottom = Math.min(windowHeight, rect.bottom); + const frameRelativeRight = Math.min(windowWidth, rect.right); + const data = { + top: Math.max(0, rect.top * -1), + height: Math.max(0, frameRelativeBottom - frameRelativeTop), + left: Math.max(0, rect.left * -1), + width: Math.max(0, frameRelativeRight - frameRelativeLeft), + }; + return data; +}; +/** + * Replaces placeholders in a template string with provided values. + * Placeholders should be in the format {key}. + * @param template - The template string with placeholders + * @param values - An object containing key-value pairs to replace placeholders + * @returns The template string with placeholders replaced + * @example + * formatTemplate('Hello {name}, you are {age} years old', { name: 'John', age: 30 }) + * // Returns: 'Hello John, you are 30 years old' + * + * formatTemplate('Expected {type}, but received {actual}', { type: 'string', actual: 'number' }) + * // Returns: 'Expected string, but received number' + */ +const formatTemplate = (template, values) => { + // This regex /\{(\w+)\}/g finds all placeholders in the format {word} + // and captures the word inside the braces for replacement. + return template.replace(/\{(\w+)\}/g, (match, key) => { + return values[key] !== undefined ? String(values[key]) : match; + }); +}; +const getHostEventsConfig = (viewConfig) => { + return { + shouldBypassPayloadValidation: viewConfig.shouldBypassPayloadValidation, + useHostEventsV2: viewConfig.useHostEventsV2, + }; +}; +/** + * Check if the window is undefined + * If the window is undefined, it means the code is running in a SSR environment. + * @returns true if the window is undefined, false otherwise + * + */ +const isWindowUndefined = () => { + if (typeof window === 'undefined') { + logger$3.error(ERROR_MESSAGE.SSR_ENVIRONMENT_ERROR); + return true; + } + return false; +}; +/** + * Validates that a URL uses only http: or https: protocols. + * Returns a tuple of [isValid, error] so the caller can handle validation errors. + * @param url - The URL string to validate + * @returns [true, null] if valid, [false, Error] if invalid + */ +const validateHttpUrl = (url) => { + try { + const parsedUrl = new URL(url); + if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') { + return [false, new Error(`Invalid protocol: ${parsedUrl.protocol}. Only http: and https: are allowed.`)]; + } + return [true, null]; + } + catch (error) { + return [false, error instanceof Error ? error : new Error(String(error))]; + } +}; +/** + * Sets a query parameter if the value is defined. + * @param queryParams - The query params object to modify + * @param param - The parameter key + * @param value - The value to set + * @param asBoolean - If true, coerces value to boolean + */ +const setParamIfDefined = (queryParams, param, value, asBoolean = false) => { + if (value !== undefined) { + queryParams[param] = asBoolean ? !!value : value; + } +}; + +/** Used for built-in method references. */ +var objectProto$c = Object.prototype; + +/** + * Checks if `value` is likely a prototype object. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a prototype, else `false`. + */ +function isPrototype(value) { + var Ctor = value && value.constructor, + proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto$c; + + return value === proto; +} + +var _isPrototype = isPrototype; + +/** + * Creates a unary function that invokes `func` with its argument transformed. + * + * @private + * @param {Function} func The function to wrap. + * @param {Function} transform The argument transform. + * @returns {Function} Returns the new function. + */ +function overArg(func, transform) { + return function(arg) { + return func(transform(arg)); + }; +} + +var _overArg = overArg; + +/* Built-in method references for those with the same name as other `lodash` methods. */ +var nativeKeys = _overArg(Object.keys, Object); + +var _nativeKeys = nativeKeys; + +/** Used for built-in method references. */ +var objectProto$b = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty$a = objectProto$b.hasOwnProperty; + +/** + * The base implementation of `_.keys` which doesn't treat sparse arrays as dense. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + */ +function baseKeys(object) { + if (!_isPrototype(object)) { + return _nativeKeys(object); + } + var result = []; + for (var key in Object(object)) { + if (hasOwnProperty$a.call(object, key) && key != 'constructor') { + result.push(key); + } + } + return result; +} + +var _baseKeys = baseKeys; + +var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; + +function createCommonjsModule(fn) { + var module = { exports: {} }; + return fn(module, module.exports), module.exports; +} + +/** Detect free variable `global` from Node.js. */ + +var freeGlobal = typeof commonjsGlobal == 'object' && commonjsGlobal && commonjsGlobal.Object === Object && commonjsGlobal; + +var _freeGlobal = freeGlobal; + +/** Detect free variable `self`. */ +var freeSelf = typeof self == 'object' && self && self.Object === Object && self; + +/** Used as a reference to the global object. */ +var root = _freeGlobal || freeSelf || Function('return this')(); + +var _root = root; + +/** Built-in value references. */ +var Symbol$1 = _root.Symbol; + +var _Symbol = Symbol$1; + +/** Used for built-in method references. */ +var objectProto$a = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty$9 = objectProto$a.hasOwnProperty; + +/** + * Used to resolve the + * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) + * of values. + */ +var nativeObjectToString$1 = objectProto$a.toString; + +/** Built-in value references. */ +var symToStringTag$1 = _Symbol ? _Symbol.toStringTag : undefined; + +/** + * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the raw `toStringTag`. + */ +function getRawTag(value) { + var isOwn = hasOwnProperty$9.call(value, symToStringTag$1), + tag = value[symToStringTag$1]; + + try { + value[symToStringTag$1] = undefined; + var unmasked = true; + } catch (e) {} + + var result = nativeObjectToString$1.call(value); + if (unmasked) { + if (isOwn) { + value[symToStringTag$1] = tag; + } else { + delete value[symToStringTag$1]; + } + } + return result; +} + +var _getRawTag = getRawTag; + +/** Used for built-in method references. */ +var objectProto$9 = Object.prototype; + +/** + * Used to resolve the + * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) + * of values. + */ +var nativeObjectToString = objectProto$9.toString; + +/** + * Converts `value` to a string using `Object.prototype.toString`. + * + * @private + * @param {*} value The value to convert. + * @returns {string} Returns the converted string. + */ +function objectToString(value) { + return nativeObjectToString.call(value); +} + +var _objectToString = objectToString; + +/** `Object#toString` result references. */ +var nullTag$1 = '[object Null]', + undefinedTag = '[object Undefined]'; + +/** Built-in value references. */ +var symToStringTag = _Symbol ? _Symbol.toStringTag : undefined; + +/** + * The base implementation of `getTag` without fallbacks for buggy environments. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the `toStringTag`. + */ +function baseGetTag(value) { + if (value == null) { + return value === undefined ? undefinedTag : nullTag$1; + } + return (symToStringTag && symToStringTag in Object(value)) + ? _getRawTag(value) + : _objectToString(value); +} + +var _baseGetTag = baseGetTag; + +/** + * Checks if `value` is the + * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) + * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(_.noop); + * // => true + * + * _.isObject(null); + * // => false + */ +function isObject(value) { + var type = typeof value; + return value != null && (type == 'object' || type == 'function'); +} + +var isObject_1 = isObject; + +/** `Object#toString` result references. */ +var asyncTag = '[object AsyncFunction]', + funcTag$1 = '[object Function]', + genTag = '[object GeneratorFunction]', + proxyTag = '[object Proxy]'; + +/** + * Checks if `value` is classified as a `Function` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a function, else `false`. + * @example + * + * _.isFunction(_); + * // => true + * + * _.isFunction(/abc/); + * // => false + */ +function isFunction(value) { + if (!isObject_1(value)) { + return false; + } + // The use of `Object#toString` avoids issues with the `typeof` operator + // in Safari 9 which returns 'object' for typed arrays and other constructors. + var tag = _baseGetTag(value); + return tag == funcTag$1 || tag == genTag || tag == asyncTag || tag == proxyTag; +} + +var isFunction_1 = isFunction; + +/** Used to detect overreaching core-js shims. */ +var coreJsData = _root['__core-js_shared__']; + +var _coreJsData = coreJsData; + +/** Used to detect methods masquerading as native. */ +var maskSrcKey = (function() { + var uid = /[^.]+$/.exec(_coreJsData && _coreJsData.keys && _coreJsData.keys.IE_PROTO || ''); + return uid ? ('Symbol(src)_1.' + uid) : ''; +}()); + +/** + * Checks if `func` has its source masked. + * + * @private + * @param {Function} func The function to check. + * @returns {boolean} Returns `true` if `func` is masked, else `false`. + */ +function isMasked(func) { + return !!maskSrcKey && (maskSrcKey in func); +} + +var _isMasked = isMasked; + +/** Used for built-in method references. */ +var funcProto$1 = Function.prototype; + +/** Used to resolve the decompiled source of functions. */ +var funcToString$1 = funcProto$1.toString; + +/** + * Converts `func` to its source code. + * + * @private + * @param {Function} func The function to convert. + * @returns {string} Returns the source code. + */ +function toSource(func) { + if (func != null) { + try { + return funcToString$1.call(func); + } catch (e) {} + try { + return (func + ''); + } catch (e) {} + } + return ''; +} + +var _toSource = toSource; + +/** + * Used to match `RegExp` + * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns). + */ +var reRegExpChar = /[\\^$.*+?()[\]{}|]/g; + +/** Used to detect host constructors (Safari). */ +var reIsHostCtor = /^\[object .+?Constructor\]$/; + +/** Used for built-in method references. */ +var funcProto = Function.prototype, + objectProto$8 = Object.prototype; + +/** Used to resolve the decompiled source of functions. */ +var funcToString = funcProto.toString; + +/** Used to check objects for own properties. */ +var hasOwnProperty$8 = objectProto$8.hasOwnProperty; + +/** Used to detect if a method is native. */ +var reIsNative = RegExp('^' + + funcToString.call(hasOwnProperty$8).replace(reRegExpChar, '\\$&') + .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' +); + +/** + * The base implementation of `_.isNative` without bad shim checks. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a native function, + * else `false`. + */ +function baseIsNative(value) { + if (!isObject_1(value) || _isMasked(value)) { + return false; + } + var pattern = isFunction_1(value) ? reIsNative : reIsHostCtor; + return pattern.test(_toSource(value)); +} + +var _baseIsNative = baseIsNative; + +/** + * Gets the value at `key` of `object`. + * + * @private + * @param {Object} [object] The object to query. + * @param {string} key The key of the property to get. + * @returns {*} Returns the property value. + */ +function getValue(object, key) { + return object == null ? undefined : object[key]; +} + +var _getValue = getValue; + +/** + * Gets the native function at `key` of `object`. + * + * @private + * @param {Object} object The object to query. + * @param {string} key The key of the method to get. + * @returns {*} Returns the function if it's native, else `undefined`. + */ +function getNative(object, key) { + var value = _getValue(object, key); + return _baseIsNative(value) ? value : undefined; +} + +var _getNative = getNative; + +/* Built-in method references that are verified to be native. */ +var DataView = _getNative(_root, 'DataView'); + +var _DataView = DataView; + +/* Built-in method references that are verified to be native. */ +var Map$1 = _getNative(_root, 'Map'); + +var _Map = Map$1; + +/* Built-in method references that are verified to be native. */ +var Promise$1 = _getNative(_root, 'Promise'); + +var _Promise = Promise$1; + +/* Built-in method references that are verified to be native. */ +var Set$1 = _getNative(_root, 'Set'); + +var _Set = Set$1; + +/* Built-in method references that are verified to be native. */ +var WeakMap = _getNative(_root, 'WeakMap'); + +var _WeakMap = WeakMap; + +/** `Object#toString` result references. */ +var mapTag$3 = '[object Map]', + objectTag$2 = '[object Object]', + promiseTag = '[object Promise]', + setTag$3 = '[object Set]', + weakMapTag$1 = '[object WeakMap]'; + +var dataViewTag$2 = '[object DataView]'; + +/** Used to detect maps, sets, and weakmaps. */ +var dataViewCtorString = _toSource(_DataView), + mapCtorString = _toSource(_Map), + promiseCtorString = _toSource(_Promise), + setCtorString = _toSource(_Set), + weakMapCtorString = _toSource(_WeakMap); + +/** + * Gets the `toStringTag` of `value`. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the `toStringTag`. + */ +var getTag = _baseGetTag; + +// Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6. +if ((_DataView && getTag(new _DataView(new ArrayBuffer(1))) != dataViewTag$2) || + (_Map && getTag(new _Map) != mapTag$3) || + (_Promise && getTag(_Promise.resolve()) != promiseTag) || + (_Set && getTag(new _Set) != setTag$3) || + (_WeakMap && getTag(new _WeakMap) != weakMapTag$1)) { + getTag = function(value) { + var result = _baseGetTag(value), + Ctor = result == objectTag$2 ? value.constructor : undefined, + ctorString = Ctor ? _toSource(Ctor) : ''; + + if (ctorString) { + switch (ctorString) { + case dataViewCtorString: return dataViewTag$2; + case mapCtorString: return mapTag$3; + case promiseCtorString: return promiseTag; + case setCtorString: return setTag$3; + case weakMapCtorString: return weakMapTag$1; + } + } + return result; + }; +} + +var _getTag = getTag; + +/** + * Checks if `value` is object-like. A value is object-like if it's not `null` + * and has a `typeof` result of "object". + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + * @example + * + * _.isObjectLike({}); + * // => true + * + * _.isObjectLike([1, 2, 3]); + * // => true + * + * _.isObjectLike(_.noop); + * // => false + * + * _.isObjectLike(null); + * // => false + */ +function isObjectLike(value) { + return value != null && typeof value == 'object'; +} + +var isObjectLike_1 = isObjectLike; + +/** `Object#toString` result references. */ +var argsTag$2 = '[object Arguments]'; + +/** + * The base implementation of `_.isArguments`. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an `arguments` object, + */ +function baseIsArguments(value) { + return isObjectLike_1(value) && _baseGetTag(value) == argsTag$2; +} + +var _baseIsArguments = baseIsArguments; + +/** Used for built-in method references. */ +var objectProto$7 = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty$7 = objectProto$7.hasOwnProperty; + +/** Built-in value references. */ +var propertyIsEnumerable$1 = objectProto$7.propertyIsEnumerable; + +/** + * Checks if `value` is likely an `arguments` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an `arguments` object, + * else `false`. + * @example + * + * _.isArguments(function() { return arguments; }()); + * // => true + * + * _.isArguments([1, 2, 3]); + * // => false + */ +var isArguments = _baseIsArguments(function() { return arguments; }()) ? _baseIsArguments : function(value) { + return isObjectLike_1(value) && hasOwnProperty$7.call(value, 'callee') && + !propertyIsEnumerable$1.call(value, 'callee'); +}; + +var isArguments_1 = isArguments; + +/** + * Checks if `value` is classified as an `Array` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array, else `false`. + * @example + * + * _.isArray([1, 2, 3]); + * // => true + * + * _.isArray(document.body.children); + * // => false + * + * _.isArray('abc'); + * // => false + * + * _.isArray(_.noop); + * // => false + */ +var isArray = Array.isArray; + +var isArray_1 = isArray; + +/** Used as references for various `Number` constants. */ +var MAX_SAFE_INTEGER$1 = 9007199254740991; + +/** + * Checks if `value` is a valid array-like length. + * + * **Note:** This method is loosely based on + * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. + * @example + * + * _.isLength(3); + * // => true + * + * _.isLength(Number.MIN_VALUE); + * // => false + * + * _.isLength(Infinity); + * // => false + * + * _.isLength('3'); + * // => false + */ +function isLength(value) { + return typeof value == 'number' && + value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER$1; +} + +var isLength_1 = isLength; + +/** + * Checks if `value` is array-like. A value is considered array-like if it's + * not a function and has a `value.length` that's an integer greater than or + * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is array-like, else `false`. + * @example + * + * _.isArrayLike([1, 2, 3]); + * // => true + * + * _.isArrayLike(document.body.children); + * // => true + * + * _.isArrayLike('abc'); + * // => true + * + * _.isArrayLike(_.noop); + * // => false + */ +function isArrayLike(value) { + return value != null && isLength_1(value.length) && !isFunction_1(value); +} + +var isArrayLike_1 = isArrayLike; + +/** + * This method returns `false`. + * + * @static + * @memberOf _ + * @since 4.13.0 + * @category Util + * @returns {boolean} Returns `false`. + * @example + * + * _.times(2, _.stubFalse); + * // => [false, false] + */ +function stubFalse() { + return false; +} + +var stubFalse_1 = stubFalse; + +var isBuffer_1 = createCommonjsModule(function (module, exports$1) { +/** Detect free variable `exports`. */ +var freeExports = exports$1 && !exports$1.nodeType && exports$1; + +/** Detect free variable `module`. */ +var freeModule = freeExports && 'object' == 'object' && module && !module.nodeType && module; + +/** Detect the popular CommonJS extension `module.exports`. */ +var moduleExports = freeModule && freeModule.exports === freeExports; + +/** Built-in value references. */ +var Buffer = moduleExports ? _root.Buffer : undefined; + +/* Built-in method references for those with the same name as other `lodash` methods. */ +var nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined; + +/** + * Checks if `value` is a buffer. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a buffer, else `false`. + * @example + * + * _.isBuffer(new Buffer(2)); + * // => true + * + * _.isBuffer(new Uint8Array(2)); + * // => false + */ +var isBuffer = nativeIsBuffer || stubFalse_1; + +module.exports = isBuffer; +}); + +/** `Object#toString` result references. */ +var argsTag$1 = '[object Arguments]', + arrayTag$1 = '[object Array]', + boolTag$2 = '[object Boolean]', + dateTag$1 = '[object Date]', + errorTag$1 = '[object Error]', + funcTag = '[object Function]', + mapTag$2 = '[object Map]', + numberTag$1 = '[object Number]', + objectTag$1 = '[object Object]', + regexpTag$1 = '[object RegExp]', + setTag$2 = '[object Set]', + stringTag$1 = '[object String]', + weakMapTag = '[object WeakMap]'; + +var arrayBufferTag$1 = '[object ArrayBuffer]', + dataViewTag$1 = '[object DataView]', + float32Tag = '[object Float32Array]', + float64Tag = '[object Float64Array]', + int8Tag = '[object Int8Array]', + int16Tag = '[object Int16Array]', + int32Tag = '[object Int32Array]', + uint8Tag = '[object Uint8Array]', + uint8ClampedTag = '[object Uint8ClampedArray]', + uint16Tag = '[object Uint16Array]', + uint32Tag = '[object Uint32Array]'; + +/** Used to identify `toStringTag` values of typed arrays. */ +var typedArrayTags = {}; +typedArrayTags[float32Tag] = typedArrayTags[float64Tag] = +typedArrayTags[int8Tag] = typedArrayTags[int16Tag] = +typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] = +typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] = +typedArrayTags[uint32Tag] = true; +typedArrayTags[argsTag$1] = typedArrayTags[arrayTag$1] = +typedArrayTags[arrayBufferTag$1] = typedArrayTags[boolTag$2] = +typedArrayTags[dataViewTag$1] = typedArrayTags[dateTag$1] = +typedArrayTags[errorTag$1] = typedArrayTags[funcTag] = +typedArrayTags[mapTag$2] = typedArrayTags[numberTag$1] = +typedArrayTags[objectTag$1] = typedArrayTags[regexpTag$1] = +typedArrayTags[setTag$2] = typedArrayTags[stringTag$1] = +typedArrayTags[weakMapTag] = false; + +/** + * The base implementation of `_.isTypedArray` without Node.js optimizations. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. + */ +function baseIsTypedArray(value) { + return isObjectLike_1(value) && + isLength_1(value.length) && !!typedArrayTags[_baseGetTag(value)]; +} + +var _baseIsTypedArray = baseIsTypedArray; + +/** + * The base implementation of `_.unary` without support for storing metadata. + * + * @private + * @param {Function} func The function to cap arguments for. + * @returns {Function} Returns the new capped function. + */ +function baseUnary(func) { + return function(value) { + return func(value); + }; +} + +var _baseUnary = baseUnary; + +var _nodeUtil = createCommonjsModule(function (module, exports$1) { +/** Detect free variable `exports`. */ +var freeExports = exports$1 && !exports$1.nodeType && exports$1; + +/** Detect free variable `module`. */ +var freeModule = freeExports && 'object' == 'object' && module && !module.nodeType && module; + +/** Detect the popular CommonJS extension `module.exports`. */ +var moduleExports = freeModule && freeModule.exports === freeExports; + +/** Detect free variable `process` from Node.js. */ +var freeProcess = moduleExports && _freeGlobal.process; + +/** Used to access faster Node.js helpers. */ +var nodeUtil = (function() { + try { + // Use `util.types` for Node.js 10+. + var types = freeModule && freeModule.require && freeModule.require('util').types; + + if (types) { + return types; + } + + // Legacy `process.binding('util')` for Node.js < 10. + return freeProcess && freeProcess.binding && freeProcess.binding('util'); + } catch (e) {} +}()); + +module.exports = nodeUtil; +}); + +/* Node.js helper references. */ +var nodeIsTypedArray = _nodeUtil && _nodeUtil.isTypedArray; + +/** + * Checks if `value` is classified as a typed array. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. + * @example + * + * _.isTypedArray(new Uint8Array); + * // => true + * + * _.isTypedArray([]); + * // => false + */ +var isTypedArray = nodeIsTypedArray ? _baseUnary(nodeIsTypedArray) : _baseIsTypedArray; + +var isTypedArray_1 = isTypedArray; + +/** `Object#toString` result references. */ +var mapTag$1 = '[object Map]', + setTag$1 = '[object Set]'; + +/** Used for built-in method references. */ +var objectProto$6 = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty$6 = objectProto$6.hasOwnProperty; + +/** + * Checks if `value` is an empty object, collection, map, or set. + * + * Objects are considered empty if they have no own enumerable string keyed + * properties. + * + * Array-like values such as `arguments` objects, arrays, buffers, strings, or + * jQuery-like collections are considered empty if they have a `length` of `0`. + * Similarly, maps and sets are considered empty if they have a `size` of `0`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is empty, else `false`. + * @example + * + * _.isEmpty(null); + * // => true + * + * _.isEmpty(true); + * // => true + * + * _.isEmpty(1); + * // => true + * + * _.isEmpty([1, 2, 3]); + * // => false + * + * _.isEmpty({ 'a': 1 }); + * // => false + */ +function isEmpty$1(value) { + if (value == null) { + return true; + } + if (isArrayLike_1(value) && + (isArray_1(value) || typeof value == 'string' || typeof value.splice == 'function' || + isBuffer_1(value) || isTypedArray_1(value) || isArguments_1(value))) { + return !value.length; + } + var tag = _getTag(value); + if (tag == mapTag$1 || tag == setTag$1) { + return !value.size; + } + if (_isPrototype(value)) { + return !_baseKeys(value).length; + } + for (var key in value) { + if (hasOwnProperty$6.call(value, key)) { + return false; + } + } + return true; +} + +var isEmpty_1 = isEmpty$1; + +var UIPassthroughEvent; +(function (UIPassthroughEvent) { + UIPassthroughEvent["PinAnswerToLiveboard"] = "addVizToPinboard"; + UIPassthroughEvent["SaveAnswer"] = "saveAnswer"; + UIPassthroughEvent["GetDiscoverabilityStatus"] = "getDiscoverabilityStatus"; + UIPassthroughEvent["GetAvailableUIPassthroughs"] = "getAvailableUiPassthroughs"; + UIPassthroughEvent["GetAnswerConfig"] = "getAnswerPageConfig"; + UIPassthroughEvent["GetLiveboardConfig"] = "getPinboardPageConfig"; + UIPassthroughEvent["GetUnsavedAnswerTML"] = "getUnsavedAnswerTML"; + UIPassthroughEvent["UpdateFilters"] = "updateFilters"; + UIPassthroughEvent["Drilldown"] = "drillDown"; + UIPassthroughEvent["GetAnswerSession"] = "getAnswerSession"; + UIPassthroughEvent["GetFilters"] = "getFilters"; + UIPassthroughEvent["GetIframeUrl"] = "getIframeUrl"; + UIPassthroughEvent["GetParameters"] = "getParameters"; + UIPassthroughEvent["GetTML"] = "getTML"; + UIPassthroughEvent["GetTabs"] = "getTabs"; + UIPassthroughEvent["GetExportRequestForCurrentPinboard"] = "getExportRequestForCurrentPinboard"; +})(UIPassthroughEvent || (UIPassthroughEvent = {})); + +const EndPoints = { + AUTH_VERIFICATION: '/callosum/v1/session/info', + SESSION_INFO: '/callosum/v1/session/info', + PREAUTH_INFO: '/prism/preauth/info', + SAML_LOGIN_TEMPLATE: (targetUrl) => `/callosum/v1/saml/login?targetURLPath=${targetUrl}`, + OIDC_LOGIN_TEMPLATE: (targetUrl) => `/callosum/v1/oidc/login?targetURLPath=${targetUrl}`, + TOKEN_LOGIN: '/callosum/v1/session/login/token', + BASIC_LOGIN: '/callosum/v1/session/login', + LOGOUT: '/callosum/v1/session/logout', + EXECUTE_TML: '/api/rest/2.0/metadata/tml/import', + EXPORT_TML: '/api/rest/2.0/metadata/tml/export', + IS_ACTIVE: '/callosum/v1/session/isactive', +}; +/** + * + * @param url + * @param options + */ +function failureLoggedFetch(url, options = {}) { + return fetch(url, options).then(async (r) => { + if (!r.ok && r.type !== 'opaqueredirect' && r.type !== 'opaque') { + logger$3.error('Failure', await r.text?.()); + } + return r; + }); +} +/** + * Service to validate a auth token against a ThoughtSpot host. + * @param thoughtSpotHost : ThoughtSpot host to verify the token against. + * @param authToken : Auth token to verify. + */ +async function verifyTokenService(thoughtSpotHost, authToken) { + const authVerificationUrl = `${thoughtSpotHost}${EndPoints.IS_ACTIVE}`; + try { + const res = await fetch(authVerificationUrl, { + headers: { + Authorization: `Bearer ${authToken}`, + 'x-requested-by': 'ThoughtSpot', + }, + credentials: 'omit', + }); + return res.ok; + } + catch (e) { + logger$3.warn(`Token Verification Service failed : ${e.message}`); + } + return false; +} +/** + * + * @param authEndpoint + */ +async function fetchAuthTokenService(authEndpoint) { + return fetch(authEndpoint); +} +/** + * + * @param thoughtSpotHost + * @param username + * @param authToken + */ +async function fetchAuthService(thoughtSpotHost, username, authToken) { + const fetchUrlParams = username + ? `username=${username}&auth_token=${authToken}` + : `auth_token=${authToken}`; + return failureLoggedFetch(`${thoughtSpotHost}${EndPoints.TOKEN_LOGIN}?${fetchUrlParams}`, { + credentials: 'include', + // We do not want to follow the redirect, as it starts giving a CORS + // error + redirect: 'manual', + }); +} +/** + * + * @param thoughtSpotHost + * @param username + * @param authToken + */ +async function fetchAuthPostService(thoughtSpotHost, username, authToken) { + const bodyPrams = username + ? `username=${encodeURIComponent(username)}&auth_token=${encodeURIComponent(authToken)}` + : `auth_token=${encodeURIComponent(authToken)}`; + return failureLoggedFetch(`${thoughtSpotHost}${EndPoints.TOKEN_LOGIN}`, { + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'x-requested-by': 'ThoughtSpot', + }, + body: bodyPrams, + credentials: 'include', + // We do not want to follow the redirect, as it starts giving a CORS + // error + redirect: 'manual', + }); +} +/** + * + * @param thoughtSpotHost + * @param username + * @param password + */ +async function fetchBasicAuthService(thoughtSpotHost, username, password) { + return failureLoggedFetch(`${thoughtSpotHost}${EndPoints.BASIC_LOGIN}`, { + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'x-requested-by': 'ThoughtSpot', + }, + body: `username=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}`, + credentials: 'include', + }); +} + +const cacheAuthTokenKey = 'cachedAuthToken'; +const getCacheAuthToken = () => getValueFromWindow(cacheAuthTokenKey); +const storeAuthTokenInCache = (token) => { + storeValueInWindow(cacheAuthTokenKey, token); +}; +// This method can be used to get the authToken using the embedConfig +/** + * + * @param embedConfig + */ +async function getAuthenticationToken(embedConfig, skipvalidation = false) { + const cachedAuthToken = getCacheAuthToken(); + // Since we don't have token validation enabled , we cannot tell if the + // cached token is valid or not. So we will always fetch a new token. + if (cachedAuthToken && !embedConfig.disableTokenVerification && !skipvalidation) { + let isCachedTokenStillValid; + try { + isCachedTokenStillValid = await validateAuthToken(embedConfig, cachedAuthToken, true); + } + catch { + isCachedTokenStillValid = false; + } + if (isCachedTokenStillValid) + return cachedAuthToken; + } + const { authEndpoint, getAuthToken } = embedConfig; + let authToken = null; + if (getAuthToken) { + authToken = await getAuthToken(); + } + else { + const response = await fetchAuthTokenService(authEndpoint); + authToken = await response.text(); + } + try { + // this will throw error if the token is not valid + await validateAuthToken(embedConfig, authToken); + } + catch (e) { + logger$3.error(`${ERROR_MESSAGE.INVALID_TOKEN_ERROR} Error : ${e.message}`); + throw e; + } + storeAuthTokenInCache(authToken); + return authToken; +} +const validateAuthToken = async (embedConfig, authToken, suppressAlert) => { + // even if token verification is disabled, we will still validate + // that the token is a string before proceeding. + if (typeof authToken !== 'string') { + const errorMessage = formatTemplate(ERROR_MESSAGE.INVALID_TOKEN_TYPE_ERROR, { + invalidType: typeof authToken, + }); + logger$3.error(errorMessage); + throw new Error(errorMessage); + } + const cachedAuthToken = getCacheAuthToken(); + if (embedConfig.disableTokenVerification) { + logger$3.info('Token verification is disabled. Assuming token is valid.'); + return true; + } + try { + const isTokenValid = await verifyTokenService(embedConfig.thoughtSpotHost, authToken); + if (isTokenValid) + return true; + } + catch { + return false; + } + if (cachedAuthToken && cachedAuthToken === authToken) { + if (!embedConfig.suppressErrorAlerts && !suppressAlert) { + alert(ERROR_MESSAGE.DUPLICATE_TOKEN_ERR); + } + throw new Error(ERROR_MESSAGE.DUPLICATE_TOKEN_ERR); + } + else { + throw new Error(ERROR_MESSAGE.INVALID_TOKEN_ERROR); + } +}; +/** + * Resets the auth token and a new token will be fetched on the next request. + * @example + * ```js + * resetCachedAuthToken(); + * ``` + * @version SDK: 1.28.0 | ThoughtSpot: * + * @group Authentication / Init + */ +const resetCachedAuthToken = () => { + storeAuthTokenInCache(null); +}; + +const configKey = 'embedConfig'; +/** + * Gets the embed configuration settings that were used to + * initialize the SDK. + * @returns {@link EmbedConfig} + * + * @example + * ```js + * import { getInitConfig } from '@thoughtspot/visual-embed-sdk'; + * // Call the getInitConfig method to retrieve the embed configuration + * const config = getInitConfig(); + * // Log the configuration settings + * console.log(config); + * ``` + * Returns the link:https://developers.thoughtspot.com/docs/Interface_EmbedConfig[EmbedConfig] + * object, which contains the configuration settings used to + * initialize the SDK, including the following: + * + * - `thoughtSpotHost` - ThoughtSpot host URL + * - `authType`: The authentication method used. For example, + * `AuthServerCookieless` for `AuthType.TrustedAuthTokenCookieless` + * - `customizations` - Style, text, and icon customization settings + * that were applied during the SDK initialization. + * + * The following JSON output shows the embed configuration + * settings returned from the code in the previous example: + * + * @example + * ```json + * { + * "thoughtSpotHost": "https://{ThoughtSpot-Host}", + * "authType": "AuthServerCookieless", + * "customizations": { + * "style": { + * "customCSS": { + * "variables": { + * "--ts-var-button--secondary-background": "#7492d5", + * "--ts-var-button--secondary--hovers-background": "#aac2f8", + * "--ts-var-root-background": "#f1f4f8" + * } + * } + * } + * }, + * "loginFailedMessage": "Login failed, please try again", + * "authTriggerText": "Authorize", + * "disableTokenVerification": true, + * "authTriggerContainer": "#your-own-div" + * } + * ``` + * @version SDK: 1.19.0 | ThoughtSpot: 9.0.0.cl, 9.0.1.sw, and later + * @group Global methods + */ +const getEmbedConfig = () => getValueFromWindow(configKey) || {}; +/** + * Sets the configuration embed was initialized with. + * And returns the new configuration. + * @param newConfig The configuration to set. + * @version SDK: 1.27.0 | ThoughtSpot: 9.8.0.cl, 9.8.1.sw, and later + * @group Global methods + */ +const setEmbedConfig = (newConfig) => { + storeValueInWindow(configKey, newConfig); + return getValueFromWindow(configKey); +}; + +/** + * Fetch wrapper that adds the authentication token to the request. + * Use this to call the ThoughtSpot APIs when using the visual embed sdk. + * The interface for this method is the same as Web `Fetch`. + * @param input + * @param init + * @example + * ```js + * tokenizedFetch("/api/rest/2.0/auth/session/user", { + * // .. fetch options .. + * }); + * ``` + * @version SDK: 1.28.0 + * @group Global methods + */ +const tokenizedFetch = async (input, init) => { + const embedConfig = getEmbedConfig(); + const options = { ...init }; + let token; + if (embedConfig.authType !== AuthType.TrustedAuthTokenCookieless) { + token = getCacheAuthToken(); + if (!token) { + return fetch(input, { ...options, credentials: 'include' }); + } + } + else { + token = await getAuthenticationToken(embedConfig); + } + const req = new Request(input, options); + if (token) { + req.headers.append('Authorization', `Bearer ${token}`); + } + return fetch(req); +}; + +/** + * + * @param root0 + * @param root0.query + * @param root0.variables + * @param root0.thoughtSpotHost + * @param root0.isCompositeQuery + */ +async function graphqlQuery({ query, variables, thoughtSpotHost, isCompositeQuery = false, }) { + const operationName = getOperationNameFromQuery(query); + try { + const response = await tokenizedFetch(`${thoughtSpotHost}/prism/?op=${operationName}`, { + method: 'POST', + headers: { + 'content-type': 'application/json;charset=UTF-8', + 'x-requested-by': 'ThoughtSpot', + accept: '*/*', + 'accept-language': 'en-us', + }, + body: JSON.stringify({ + operationName, + query, + variables, + }), + credentials: 'include', + }); + const result = await response.json(); + const dataValues = Object.values(result.data); + return (isCompositeQuery) ? result.data : dataValues[0]; + } + catch (error) { + return error; + } +} + +const getSourceDetailQuery = ` + query GetSourceDetail($ids: [GUID!]!) { + getSourceDetailById(ids: $ids, type: LOGICAL_TABLE) { + id + name + description + authorName + authorDisplayName + isExternal + type + created + modified + columns { + id + name + author + authorDisplayName + description + dataType + type + modified + ownerName + owner + dataRecency + sources { + tableId + tableName + columnId + columnName + __typename + } + synonyms + cohortAnswerId + __typename + } + relationships + destinationRelationships + dataSourceId + __typename + } + } +`; +const sourceDetailCache = new Map(); +/** + * + * @param thoughtSpotHost + * @param sourceId + */ +async function getSourceDetail(thoughtSpotHost, sourceId) { + if (sourceDetailCache.get(sourceId)) { + return sourceDetailCache.get(sourceId); + } + const details = await graphqlQuery({ + query: getSourceDetailQuery, + variables: { + ids: [sourceId], + }, + thoughtSpotHost, + }); + const souceDetails = details[0]; + if (souceDetails) { + sourceDetailCache.set(sourceId, souceDetails); + } + return souceDetails; +} + +const bachSessionId = ` +id { + sessionId + genNo + acSession { + sessionId + genNo + } +} +`; +const getUnaggregatedAnswerSession = ` +mutation GetUnAggregatedAnswerSession($session: BachSessionIdInput!, $columns: [UserPointSelectionInput!]!) { + Answer__getUnaggregatedAnswer(session: $session, columns: $columns) { + ${bachSessionId} + answer { + visualizations { + ... on TableViz { + columns { + column { + id + name + referencedColumns { + guid + displayName + } + } + } + } + } + } + } +} +`; +const removeColumns = ` +mutation RemoveColumns($session: BachSessionIdInput!, $logicalColumnIds: [GUID!], $columnIds: [GUID!]) { + Answer__removeColumns( + session: $session + logicalColumnIds: $logicalColumnIds + columnIds: $columnIds + ) { + ${bachSessionId} + } +} + `; +const addColumns = ` + mutation AddColumns($session: BachSessionIdInput!, $columns: [AnswerColumnInfo!]!) { + Answer__addColumn(session: $session, columns: $columns) { + ${bachSessionId} + } + } + `; +const addFilter = ` + mutation AddUpdateFilter($session: BachSessionIdInput!, $params: AddUpdateFilterInput!) { + Answer__addUpdateFilter(session: $session, params: $params) { + ${bachSessionId} + } + } +`; +const getAnswer = ` + query GetAnswer($session: BachSessionIdInput!) { + getAnswer(session: $session) { + ${bachSessionId} + answer { + id + name + description + displayMode + sources { + header { + guid + displayName + } + } + filterGroups { + columnInfo { + name + referencedColumns { + guid + displayName + } + } + filters { + filterContent { + filterType + negate + value { + key + } + } + } + } + metadata { + author + authorId + createdAt + isDiscoverable + isHidden + modifiedAt + } + visualizations { + ... on TableViz { + id + columns { + column { + id + name + referencedColumns { + guid + displayName + } + } + } + } + ... on ChartViz { + id + } + } + } + } + } + +`; +const getAnswerData = ` + query GetTableWithHeadlineData($session: BachSessionIdInput!, $deadline: Int!, $dataPaginationParams: DataPaginationParamsInput!) { + getAnswer(session: $session) { + ${bachSessionId} + answer { + id + visualizations { + id + ... on TableViz { + columns { + column { + id + name + type + aggregationType + dataType + } + } + data(deadline: $deadline, pagination: $dataPaginationParams) + } + } + } + } + } +`; +const addVizToLiveboard = ` + mutation AddVizToLiveboard(liveboardId: GUID!, session: BachSessionIdInput!, tabId: GUID, vizId: GUID!) { + Answer__addVizToPinboard( + pinboardId: liveboardId + + session: $session + + tabId: $tabId + + vizId: $vizId + ) { + ${bachSessionId} + } + } +`; +const getSQLQuery = ` + mutation GetSQLQuery($session: BachSessionIdInput!) { + Answer__getQuery(session: $session) { + sql + } + } +`; +const updateDisplayMode = ` + mutation UpdateDisplayMode( + $session: BachSessionIdInput! + $displayMode: DisplayMode +) { + Answer__updateProperties(session: $session, displayMode: $displayMode) { + id { + sessionId + genNo + acSession { + sessionId + genNo + } + } + answer { + id + displayMode + suggestedDisplayMode + } + } +} +`; +const getAnswerTML = ` +mutation GetUnsavedAnswerTML($session: BachSessionIdInput!, $exportDependencies: Boolean, $formatType: EDocFormatType, $exportPermissions: Boolean, $exportFqn: Boolean) { + UnsavedAnswer_getTML( + session: $session + exportDependencies: $exportDependencies + formatType: $formatType + exportPermissions: $exportPermissions + exportFqn: $exportFqn + ) { + zipFile + object { + edoc + name + type + __typename + } + __typename + } +}`; + +// import YAML from 'yaml'; +var OperationType; +(function (OperationType) { + OperationType["GetChartWithData"] = "GetChartWithData"; + OperationType["GetTableWithHeadlineData"] = "GetTableWithHeadlineData"; +})(OperationType || (OperationType = {})); +const DATA_TYPES = ['DATE', 'DATE_TIME', 'TIME']; +/** + * AnswerService provides a simple way to work with ThoughtSpot Answers. + * + * This service allows you to interact with ThoughtSpot Answers programmatically, + * making it easy to customize visualizations, filter data, and extract insights + * directly from your application. + * + * You can use this service to: + * + * - Add or remove columns from Answers (`addColumns`, `removeColumns`, + * `addColumnsByName`) + * - Apply filters to Answers (`addFilter`) + * - Get data from Answers in different formats (JSON, CSV, PNG) (`fetchData`, + * `fetchCSVBlob`, `fetchPNGBlob`) + * - Get data for specific points in visualizations + * (`getUnderlyingDataForPoint`) + * - Run custom queries (`executeQuery`) + * - Add visualizations to Liveboards (`addDisplayedVizToLiveboard`) + * + * @example + * ```js + * // Get the answer service + * embed.on(EmbedEvent.Data, async (e) => { + * const service = await embed.getAnswerService(); + * + * // Add columns to the answer + * await service.addColumnsByName(["Sales", "Region"]); + * + * // Get the data + * const data = await service.fetchData(); + * console.log(data); + * }); + * ``` + * + * @example + * ```js + * // Get data for a point in a visualization + * embed.on(EmbedEvent.CustomAction, async (e) => { + * const underlying = await e.answerService.getUnderlyingDataForPoint([ + * 'Product Name', + * 'Sales Amount' + * ]); + * + * const data = await underlying.fetchData(0, 100); + * console.log(data); + * }); + * ``` + * + * @version SDK: 1.25.0 | ThoughtSpot: 9.10.0.cl + * @group Events + */ +class AnswerService { + /** + * Should not need to be called directly. + * @param session + * @param answer + * @param thoughtSpotHost + * @param selectedPoints + */ + constructor(session, answer, thoughtSpotHost, selectedPoints) { + this.session = session; + this.thoughtSpotHost = thoughtSpotHost; + this.selectedPoints = selectedPoints; + this.tmlOverride = {}; + this.session = removeTypename(session); + this.answer = answer; + } + /** + * Get the details about the source used in the answer. + * This can be used to get the list of all columns in the data source for example. + */ + async getSourceDetail() { + const sourceId = (await this.getAnswer()).sources[0].header.guid; + return getSourceDetail(this.thoughtSpotHost, sourceId); + } + /** + * Remove columnIds and return updated answer session. + * @param columnIds + * @returns + */ + async removeColumns(columnIds) { + return this.executeQuery(removeColumns, { + logicalColumnIds: columnIds, + }); + } + /** + * Add columnIds and return updated answer session. + * @param columnIds + * @returns + */ + async addColumns(columnIds) { + return this.executeQuery(addColumns, { + columns: columnIds.map((colId) => ({ logicalColumnId: colId })), + }); + } + /** + * Add columns by names and return updated answer session. + * @param columnNames + * @returns + * @example + * ```js + * embed.on(EmbedEvent.Data, async (e) => { + * const service = await embed.getAnswerService(); + * await service.addColumnsByName([ + * "col name 1", + * "col name 2" + * ]); + * console.log(await service.fetchData()); + * }); + * ``` + */ + async addColumnsByName(columnNames) { + const sourceDetail = await this.getSourceDetail(); + const columnGuids = getGuidsFromColumnNames(sourceDetail, columnNames); + return this.addColumns([...columnGuids]); + } + /** + * Add a filter to the answer. + * @param columnName + * @param operator + * @param values + * @returns + */ + async addFilter(columnName, operator, values) { + const sourceDetail = await this.getSourceDetail(); + const columnGuids = getGuidsFromColumnNames(sourceDetail, [columnName]); + return this.executeQuery(addFilter, { + params: { + filterContent: [{ + filterType: operator, + value: values.map((v) => { + const [type, prefix] = getTypeFromValue(v); + return { + type: type.toUpperCase(), + [`${prefix}Value`]: v, + }; + }), + }], + filterGroupId: { + logicalColumnId: columnGuids.values().next().value, + }, + }, + }); + } + async updateDisplayMode(displayMode = "TABLE_MODE") { + return this.executeQuery(updateDisplayMode, { + displayMode, + }); + } + async getSQLQuery(fetchSQLWithAllColumns = false) { + if (fetchSQLWithAllColumns) { + await this.updateDisplayMode("TABLE_MODE"); + } + const { sql } = await this.executeQuery(getSQLQuery, {}); + return sql; + } + /** + * Fetch data from the answer. + * @param offset + * @param size + * @returns + */ + async fetchData(offset = 0, size = 1000) { + const { answer } = await this.executeQuery(getAnswerData, { + deadline: 0, + dataPaginationParams: { + isClientPaginated: true, + offset, + size, + }, + }); + const { columns, data } = answer.visualizations.find((viz) => !!viz.data) || {}; + return { + columns, + data, + }; + } + /** + * Fetch the data for the answer as a CSV blob. This might be + * quicker for larger data. + * @param userLocale + * @param includeInfo Include the CSV header in the output + * @returns Response + */ + async fetchCSVBlob(userLocale = 'en-us', includeInfo = false) { + const fetchUrl = this.getFetchCSVBlobUrl(userLocale, includeInfo); + return tokenizedFetch(fetchUrl, { + credentials: 'include', + }); + } + /** + * Fetch the data for the answer as a PNG blob. This might be + * quicker for larger data. + * @param userLocale + * @param includeInfo + * @param omitBackground Omit the background in the PNG + * @param deviceScaleFactor The scale factor for the PNG + * @return Response + */ + async fetchPNGBlob(userLocale = 'en-us', omitBackground = false, deviceScaleFactor = 2) { + const fetchUrl = this.getFetchPNGBlobUrl(userLocale, omitBackground, deviceScaleFactor); + return tokenizedFetch(fetchUrl, { + credentials: 'include', + }); + } + /** + * Just get the internal URL for this answer's data + * as a CSV blob. + * @param userLocale + * @param includeInfo + * @returns + */ + getFetchCSVBlobUrl(userLocale = 'en-us', includeInfo = false) { + return `${this.thoughtSpotHost}/prism/download/answer/csv?sessionId=${this.session.sessionId}&genNo=${this.session.genNo}&userLocale=${userLocale}&exportFileName=data&hideCsvHeader=${!includeInfo}`; + } + /** + * Just get the internal URL for this answer's data + * as a PNG blob. + * @param userLocale + * @param omitBackground + * @param deviceScaleFactor + */ + getFetchPNGBlobUrl(userLocale = 'en-us', omitBackground = false, deviceScaleFactor = 2) { + return `${this.thoughtSpotHost}/prism/download/answer/png?sessionId=${this.session.sessionId}&deviceScaleFactor=${deviceScaleFactor}&omitBackground=${omitBackground}&genNo=${this.session.genNo}&userLocale=${userLocale}&exportFileName=data`; + } + /** + * Get underlying data given a point and the output column names. + * In case of a context menu action, the selectedPoints are + * automatically passed. + * @param outputColumnNames + * @param selectedPoints + * @example + * ```js + * embed.on(EmbedEvent.CustomAction, e => { + * const underlying = await e.answerService.getUnderlyingDataForPoint([ + * 'col name 1' // The column should exist in the data source. + * ]); + * const data = await underlying.fetchData(0, 100); + * }) + * ``` + * @version SDK: 1.25.0 | ThoughtSpot: 9.10.0.cl + */ + async getUnderlyingDataForPoint(outputColumnNames, selectedPoints) { + if (!selectedPoints && !this.selectedPoints) { + throw new Error('Needs to be triggered in context of a point'); + } + if (!selectedPoints) { + selectedPoints = getSelectedPointsForUnderlyingDataQuery(this.selectedPoints); + } + const sourceDetail = await this.getSourceDetail(); + const ouputColumnGuids = getGuidsFromColumnNames(sourceDetail, outputColumnNames); + const unAggAnswer = await graphqlQuery({ + query: getUnaggregatedAnswerSession, + variables: { + session: this.session, + columns: selectedPoints, + }, + thoughtSpotHost: this.thoughtSpotHost, + }); + const unaggAnswerSession = new AnswerService(unAggAnswer.id, unAggAnswer.answer, this.thoughtSpotHost); + const currentColumns = new Set(unAggAnswer.answer.visualizations[0].columns + .map((c) => c.column.referencedColumns[0].guid)); + const columnsToAdd = [...ouputColumnGuids].filter((col) => !currentColumns.has(col)); + if (columnsToAdd.length) { + await unaggAnswerSession.addColumns(columnsToAdd); + } + const columnsToRemove = [...currentColumns].filter((col) => !ouputColumnGuids.has(col)); + if (columnsToRemove.length) { + await unaggAnswerSession.removeColumns(columnsToRemove); + } + return unaggAnswerSession; + } + /** + * Execute a custom graphql query in the context of the answer. + * @param query graphql query + * @param variables graphql variables + * @returns + */ + async executeQuery(query, variables) { + const data = await graphqlQuery({ + query, + variables: { + session: this.session, + ...variables, + }, + thoughtSpotHost: this.thoughtSpotHost, + isCompositeQuery: false, + }); + this.session = deepMerge(this.session, data?.id || {}); + return data; + } + /** + * Get the internal session details for the answer. + * @returns + */ + getSession() { + return this.session; + } + async getAnswer() { + if (this.answer) { + return this.answer; + } + this.answer = this.executeQuery(getAnswer, {}).then((data) => data?.answer); + return this.answer; + } + async getTML() { + const { object } = await this.executeQuery(getAnswerTML, {}); + const edoc = object[0].edoc; + const YAML = await import('./index-Ck-r09gt.js'); + const parsedDoc = YAML.parse(edoc); + return { + answer: { + ...parsedDoc.answer, + ...this.tmlOverride, + }, + }; + } + async addDisplayedVizToLiveboard(liveboardId) { + const { displayMode, visualizations } = await this.getAnswer(); + const viz = getDisplayedViz(visualizations, displayMode); + return this.executeQuery(addVizToLiveboard, { + liveboardId, + vizId: viz.id, + }); + } + setTMLOverride(override) { + this.tmlOverride = override; + } +} +/** + * + * @param sourceDetail + * @param colNames + */ +function getGuidsFromColumnNames(sourceDetail, colNames) { + const cols = sourceDetail.columns.reduce((colSet, col) => { + colSet[col.name.toLowerCase()] = col; + return colSet; + }, {}); + return new Set(colNames.map((colName) => { + const col = cols[colName.toLowerCase()]; + return col.id; + })); +} +/** + * + * @param selectedPoints + */ +function getSelectedPointsForUnderlyingDataQuery(selectedPoints) { + const underlyingDataPoint = []; + /** + * + * @param colVal + */ + function addPointFromColVal(colVal) { + const dataType = colVal.column.dataType; + colVal.column.id; + let dataValue; + if (DATA_TYPES.includes(dataType)) { + if (Number.isFinite(colVal.value)) { + dataValue = [{ + epochRange: { + startEpoch: colVal.value, + }, + }]; + // Case for custom calendar. + } + else if (colVal.value?.v) { + dataValue = [{ + epochRange: { + startEpoch: colVal.value.v.s, + endEpoch: colVal.value.v.e, + }, + }]; + } + } + else { + dataValue = [{ value: colVal.value }]; + } + underlyingDataPoint.push({ + columnId: colVal.column.id, + dataValue, + }); + } + selectedPoints.forEach((p) => { + p.selectedAttributes.forEach(addPointFromColVal); + }); + return underlyingDataPoint; +} +/** + * + * @param visualizations + * @param displayMode + */ +function getDisplayedViz(visualizations, displayMode) { + if (displayMode === 'CHART_MODE') { + return visualizations.find((viz) => viz.__typename === 'ChartViz'); + } + return visualizations.find((viz) => viz.__typename === 'TableViz'); +} + +/** + * Appends the elements of `values` to `array`. + * + * @private + * @param {Array} array The array to modify. + * @param {Array} values The values to append. + * @returns {Array} Returns `array`. + */ +function arrayPush(array, values) { + var index = -1, + length = values.length, + offset = array.length; + + while (++index < length) { + array[offset + index] = values[index]; + } + return array; +} + +var _arrayPush = arrayPush; + +/** Built-in value references. */ +var spreadableSymbol = _Symbol ? _Symbol.isConcatSpreadable : undefined; + +/** + * Checks if `value` is a flattenable `arguments` object or array. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is flattenable, else `false`. + */ +function isFlattenable(value) { + return isArray_1(value) || isArguments_1(value) || + !!(spreadableSymbol && value && value[spreadableSymbol]); +} + +var _isFlattenable = isFlattenable; + +/** + * The base implementation of `_.flatten` with support for restricting flattening. + * + * @private + * @param {Array} array The array to flatten. + * @param {number} depth The maximum recursion depth. + * @param {boolean} [predicate=isFlattenable] The function invoked per iteration. + * @param {boolean} [isStrict] Restrict to values that pass `predicate` checks. + * @param {Array} [result=[]] The initial result value. + * @returns {Array} Returns the new flattened array. + */ +function baseFlatten(array, depth, predicate, isStrict, result) { + var index = -1, + length = array.length; + + predicate || (predicate = _isFlattenable); + result || (result = []); + + while (++index < length) { + var value = array[index]; + if (depth > 0 && predicate(value)) { + if (depth > 1) { + // Recursively flatten arrays (susceptible to call stack limits). + baseFlatten(value, depth - 1, predicate, isStrict, result); + } else { + _arrayPush(result, value); + } + } else if (!isStrict) { + result[result.length] = value; + } + } + return result; +} + +var _baseFlatten = baseFlatten; + +/** + * A specialized version of `_.map` for arrays without support for iteratee + * shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the new mapped array. + */ +function arrayMap(array, iteratee) { + var index = -1, + length = array == null ? 0 : array.length, + result = Array(length); + + while (++index < length) { + result[index] = iteratee(array[index], index, array); + } + return result; +} + +var _arrayMap = arrayMap; + +/** `Object#toString` result references. */ +var symbolTag$1 = '[object Symbol]'; + +/** + * Checks if `value` is classified as a `Symbol` primitive or object. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. + * @example + * + * _.isSymbol(Symbol.iterator); + * // => true + * + * _.isSymbol('abc'); + * // => false + */ +function isSymbol(value) { + return typeof value == 'symbol' || + (isObjectLike_1(value) && _baseGetTag(value) == symbolTag$1); +} + +var isSymbol_1 = isSymbol; + +/** Used to match property names within property paths. */ +var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/, + reIsPlainProp = /^\w*$/; + +/** + * Checks if `value` is a property name and not a property path. + * + * @private + * @param {*} value The value to check. + * @param {Object} [object] The object to query keys on. + * @returns {boolean} Returns `true` if `value` is a property name, else `false`. + */ +function isKey(value, object) { + if (isArray_1(value)) { + return false; + } + var type = typeof value; + if (type == 'number' || type == 'symbol' || type == 'boolean' || + value == null || isSymbol_1(value)) { + return true; + } + return reIsPlainProp.test(value) || !reIsDeepProp.test(value) || + (object != null && value in Object(object)); +} + +var _isKey = isKey; + +/* Built-in method references that are verified to be native. */ +var nativeCreate = _getNative(Object, 'create'); + +var _nativeCreate = nativeCreate; + +/** + * Removes all key-value entries from the hash. + * + * @private + * @name clear + * @memberOf Hash + */ +function hashClear() { + this.__data__ = _nativeCreate ? _nativeCreate(null) : {}; + this.size = 0; +} + +var _hashClear = hashClear; + +/** + * Removes `key` and its value from the hash. + * + * @private + * @name delete + * @memberOf Hash + * @param {Object} hash The hash to modify. + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ +function hashDelete(key) { + var result = this.has(key) && delete this.__data__[key]; + this.size -= result ? 1 : 0; + return result; +} + +var _hashDelete = hashDelete; + +/** Used to stand-in for `undefined` hash values. */ +var HASH_UNDEFINED$2 = '__lodash_hash_undefined__'; + +/** Used for built-in method references. */ +var objectProto$5 = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty$5 = objectProto$5.hasOwnProperty; + +/** + * Gets the hash value for `key`. + * + * @private + * @name get + * @memberOf Hash + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ +function hashGet(key) { + var data = this.__data__; + if (_nativeCreate) { + var result = data[key]; + return result === HASH_UNDEFINED$2 ? undefined : result; + } + return hasOwnProperty$5.call(data, key) ? data[key] : undefined; +} + +var _hashGet = hashGet; + +/** Used for built-in method references. */ +var objectProto$4 = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty$4 = objectProto$4.hasOwnProperty; + +/** + * Checks if a hash value for `key` exists. + * + * @private + * @name has + * @memberOf Hash + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ +function hashHas(key) { + var data = this.__data__; + return _nativeCreate ? (data[key] !== undefined) : hasOwnProperty$4.call(data, key); +} + +var _hashHas = hashHas; + +/** Used to stand-in for `undefined` hash values. */ +var HASH_UNDEFINED$1 = '__lodash_hash_undefined__'; + +/** + * Sets the hash `key` to `value`. + * + * @private + * @name set + * @memberOf Hash + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the hash instance. + */ +function hashSet(key, value) { + var data = this.__data__; + this.size += this.has(key) ? 0 : 1; + data[key] = (_nativeCreate && value === undefined) ? HASH_UNDEFINED$1 : value; + return this; +} + +var _hashSet = hashSet; + +/** + * Creates a hash object. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ +function Hash(entries) { + var index = -1, + length = entries == null ? 0 : entries.length; + + this.clear(); + while (++index < length) { + var entry = entries[index]; + this.set(entry[0], entry[1]); + } +} + +// Add methods to `Hash`. +Hash.prototype.clear = _hashClear; +Hash.prototype['delete'] = _hashDelete; +Hash.prototype.get = _hashGet; +Hash.prototype.has = _hashHas; +Hash.prototype.set = _hashSet; + +var _Hash = Hash; + +/** + * Removes all key-value entries from the list cache. + * + * @private + * @name clear + * @memberOf ListCache + */ +function listCacheClear() { + this.__data__ = []; + this.size = 0; +} + +var _listCacheClear = listCacheClear; + +/** + * Performs a + * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * comparison between two values to determine if they are equivalent. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + * @example + * + * var object = { 'a': 1 }; + * var other = { 'a': 1 }; + * + * _.eq(object, object); + * // => true + * + * _.eq(object, other); + * // => false + * + * _.eq('a', 'a'); + * // => true + * + * _.eq('a', Object('a')); + * // => false + * + * _.eq(NaN, NaN); + * // => true + */ +function eq(value, other) { + return value === other || (value !== value && other !== other); +} + +var eq_1 = eq; + +/** + * Gets the index at which the `key` is found in `array` of key-value pairs. + * + * @private + * @param {Array} array The array to inspect. + * @param {*} key The key to search for. + * @returns {number} Returns the index of the matched value, else `-1`. + */ +function assocIndexOf(array, key) { + var length = array.length; + while (length--) { + if (eq_1(array[length][0], key)) { + return length; + } + } + return -1; +} + +var _assocIndexOf = assocIndexOf; + +/** Used for built-in method references. */ +var arrayProto = Array.prototype; + +/** Built-in value references. */ +var splice = arrayProto.splice; + +/** + * Removes `key` and its value from the list cache. + * + * @private + * @name delete + * @memberOf ListCache + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ +function listCacheDelete(key) { + var data = this.__data__, + index = _assocIndexOf(data, key); + + if (index < 0) { + return false; + } + var lastIndex = data.length - 1; + if (index == lastIndex) { + data.pop(); + } else { + splice.call(data, index, 1); + } + --this.size; + return true; +} + +var _listCacheDelete = listCacheDelete; + +/** + * Gets the list cache value for `key`. + * + * @private + * @name get + * @memberOf ListCache + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ +function listCacheGet(key) { + var data = this.__data__, + index = _assocIndexOf(data, key); + + return index < 0 ? undefined : data[index][1]; +} + +var _listCacheGet = listCacheGet; + +/** + * Checks if a list cache value for `key` exists. + * + * @private + * @name has + * @memberOf ListCache + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ +function listCacheHas(key) { + return _assocIndexOf(this.__data__, key) > -1; +} + +var _listCacheHas = listCacheHas; + +/** + * Sets the list cache `key` to `value`. + * + * @private + * @name set + * @memberOf ListCache + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the list cache instance. + */ +function listCacheSet(key, value) { + var data = this.__data__, + index = _assocIndexOf(data, key); + + if (index < 0) { + ++this.size; + data.push([key, value]); + } else { + data[index][1] = value; + } + return this; +} + +var _listCacheSet = listCacheSet; + +/** + * Creates an list cache object. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ +function ListCache(entries) { + var index = -1, + length = entries == null ? 0 : entries.length; + + this.clear(); + while (++index < length) { + var entry = entries[index]; + this.set(entry[0], entry[1]); + } +} + +// Add methods to `ListCache`. +ListCache.prototype.clear = _listCacheClear; +ListCache.prototype['delete'] = _listCacheDelete; +ListCache.prototype.get = _listCacheGet; +ListCache.prototype.has = _listCacheHas; +ListCache.prototype.set = _listCacheSet; + +var _ListCache = ListCache; + +/** + * Removes all key-value entries from the map. + * + * @private + * @name clear + * @memberOf MapCache + */ +function mapCacheClear() { + this.size = 0; + this.__data__ = { + 'hash': new _Hash, + 'map': new (_Map || _ListCache), + 'string': new _Hash + }; +} + +var _mapCacheClear = mapCacheClear; + +/** + * Checks if `value` is suitable for use as unique object key. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is suitable, else `false`. + */ +function isKeyable(value) { + var type = typeof value; + return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean') + ? (value !== '__proto__') + : (value === null); +} + +var _isKeyable = isKeyable; + +/** + * Gets the data for `map`. + * + * @private + * @param {Object} map The map to query. + * @param {string} key The reference key. + * @returns {*} Returns the map data. + */ +function getMapData(map, key) { + var data = map.__data__; + return _isKeyable(key) + ? data[typeof key == 'string' ? 'string' : 'hash'] + : data.map; +} + +var _getMapData = getMapData; + +/** + * Removes `key` and its value from the map. + * + * @private + * @name delete + * @memberOf MapCache + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ +function mapCacheDelete(key) { + var result = _getMapData(this, key)['delete'](key); + this.size -= result ? 1 : 0; + return result; +} + +var _mapCacheDelete = mapCacheDelete; + +/** + * Gets the map value for `key`. + * + * @private + * @name get + * @memberOf MapCache + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ +function mapCacheGet(key) { + return _getMapData(this, key).get(key); +} + +var _mapCacheGet = mapCacheGet; + +/** + * Checks if a map value for `key` exists. + * + * @private + * @name has + * @memberOf MapCache + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ +function mapCacheHas(key) { + return _getMapData(this, key).has(key); +} + +var _mapCacheHas = mapCacheHas; + +/** + * Sets the map `key` to `value`. + * + * @private + * @name set + * @memberOf MapCache + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the map cache instance. + */ +function mapCacheSet(key, value) { + var data = _getMapData(this, key), + size = data.size; + + data.set(key, value); + this.size += data.size == size ? 0 : 1; + return this; +} + +var _mapCacheSet = mapCacheSet; + +/** + * Creates a map cache object to store key-value pairs. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ +function MapCache(entries) { + var index = -1, + length = entries == null ? 0 : entries.length; + + this.clear(); + while (++index < length) { + var entry = entries[index]; + this.set(entry[0], entry[1]); + } +} + +// Add methods to `MapCache`. +MapCache.prototype.clear = _mapCacheClear; +MapCache.prototype['delete'] = _mapCacheDelete; +MapCache.prototype.get = _mapCacheGet; +MapCache.prototype.has = _mapCacheHas; +MapCache.prototype.set = _mapCacheSet; + +var _MapCache = MapCache; + +/** Error message constants. */ +var FUNC_ERROR_TEXT = 'Expected a function'; + +/** + * Creates a function that memoizes the result of `func`. If `resolver` is + * provided, it determines the cache key for storing the result based on the + * arguments provided to the memoized function. By default, the first argument + * provided to the memoized function is used as the map cache key. The `func` + * is invoked with the `this` binding of the memoized function. + * + * **Note:** The cache is exposed as the `cache` property on the memoized + * function. Its creation may be customized by replacing the `_.memoize.Cache` + * constructor with one whose instances implement the + * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object) + * method interface of `clear`, `delete`, `get`, `has`, and `set`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Function + * @param {Function} func The function to have its output memoized. + * @param {Function} [resolver] The function to resolve the cache key. + * @returns {Function} Returns the new memoized function. + * @example + * + * var object = { 'a': 1, 'b': 2 }; + * var other = { 'c': 3, 'd': 4 }; + * + * var values = _.memoize(_.values); + * values(object); + * // => [1, 2] + * + * values(other); + * // => [3, 4] + * + * object.a = 2; + * values(object); + * // => [1, 2] + * + * // Modify the result cache. + * values.cache.set(object, ['a', 'b']); + * values(object); + * // => ['a', 'b'] + * + * // Replace `_.memoize.Cache`. + * _.memoize.Cache = WeakMap; + */ +function memoize(func, resolver) { + if (typeof func != 'function' || (resolver != null && typeof resolver != 'function')) { + throw new TypeError(FUNC_ERROR_TEXT); + } + var memoized = function() { + var args = arguments, + key = resolver ? resolver.apply(this, args) : args[0], + cache = memoized.cache; + + if (cache.has(key)) { + return cache.get(key); + } + var result = func.apply(this, args); + memoized.cache = cache.set(key, result) || cache; + return result; + }; + memoized.cache = new (memoize.Cache || _MapCache); + return memoized; +} + +// Expose `MapCache`. +memoize.Cache = _MapCache; + +var memoize_1 = memoize; + +/** Used as the maximum memoize cache size. */ +var MAX_MEMOIZE_SIZE = 500; + +/** + * A specialized version of `_.memoize` which clears the memoized function's + * cache when it exceeds `MAX_MEMOIZE_SIZE`. + * + * @private + * @param {Function} func The function to have its output memoized. + * @returns {Function} Returns the new memoized function. + */ +function memoizeCapped(func) { + var result = memoize_1(func, function(key) { + if (cache.size === MAX_MEMOIZE_SIZE) { + cache.clear(); + } + return key; + }); + + var cache = result.cache; + return result; +} + +var _memoizeCapped = memoizeCapped; + +/** Used to match property names within property paths. */ +var rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g; + +/** Used to match backslashes in property paths. */ +var reEscapeChar = /\\(\\)?/g; + +/** + * Converts `string` to a property path array. + * + * @private + * @param {string} string The string to convert. + * @returns {Array} Returns the property path array. + */ +var stringToPath = _memoizeCapped(function(string) { + var result = []; + if (string.charCodeAt(0) === 46 /* . */) { + result.push(''); + } + string.replace(rePropName, function(match, number, quote, subString) { + result.push(quote ? subString.replace(reEscapeChar, '$1') : (number || match)); + }); + return result; +}); + +var _stringToPath = stringToPath; + +/** Used to convert symbols to primitives and strings. */ +var symbolProto$1 = _Symbol ? _Symbol.prototype : undefined, + symbolToString = symbolProto$1 ? symbolProto$1.toString : undefined; + +/** + * The base implementation of `_.toString` which doesn't convert nullish + * values to empty strings. + * + * @private + * @param {*} value The value to process. + * @returns {string} Returns the string. + */ +function baseToString(value) { + // Exit early for strings to avoid a performance hit in some environments. + if (typeof value == 'string') { + return value; + } + if (isArray_1(value)) { + // Recursively convert values (susceptible to call stack limits). + return _arrayMap(value, baseToString) + ''; + } + if (isSymbol_1(value)) { + return symbolToString ? symbolToString.call(value) : ''; + } + var result = (value + ''); + return (result == '0' && (1 / value) == -Infinity) ? '-0' : result; +} + +var _baseToString = baseToString; + +/** + * Converts `value` to a string. An empty string is returned for `null` + * and `undefined` values. The sign of `-0` is preserved. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {string} Returns the converted string. + * @example + * + * _.toString(null); + * // => '' + * + * _.toString(-0); + * // => '-0' + * + * _.toString([1, 2, 3]); + * // => '1,2,3' + */ +function toString$1(value) { + return value == null ? '' : _baseToString(value); +} + +var toString_1 = toString$1; + +/** + * Casts `value` to a path array if it's not one. + * + * @private + * @param {*} value The value to inspect. + * @param {Object} [object] The object to query keys on. + * @returns {Array} Returns the cast property path array. + */ +function castPath(value, object) { + if (isArray_1(value)) { + return value; + } + return _isKey(value, object) ? [value] : _stringToPath(toString_1(value)); +} + +var _castPath = castPath; + +/** + * Converts `value` to a string key if it's not a string or symbol. + * + * @private + * @param {*} value The value to inspect. + * @returns {string|symbol} Returns the key. + */ +function toKey(value) { + if (typeof value == 'string' || isSymbol_1(value)) { + return value; + } + var result = (value + ''); + return (result == '0' && (1 / value) == -Infinity) ? '-0' : result; +} + +var _toKey = toKey; + +/** + * The base implementation of `_.get` without support for default values. + * + * @private + * @param {Object} object The object to query. + * @param {Array|string} path The path of the property to get. + * @returns {*} Returns the resolved value. + */ +function baseGet(object, path) { + path = _castPath(path, object); + + var index = 0, + length = path.length; + + while (object != null && index < length) { + object = object[_toKey(path[index++])]; + } + return (index && index == length) ? object : undefined; +} + +var _baseGet = baseGet; + +/** + * Removes all key-value entries from the stack. + * + * @private + * @name clear + * @memberOf Stack + */ +function stackClear() { + this.__data__ = new _ListCache; + this.size = 0; +} + +var _stackClear = stackClear; + +/** + * Removes `key` and its value from the stack. + * + * @private + * @name delete + * @memberOf Stack + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ +function stackDelete(key) { + var data = this.__data__, + result = data['delete'](key); + + this.size = data.size; + return result; +} + +var _stackDelete = stackDelete; + +/** + * Gets the stack value for `key`. + * + * @private + * @name get + * @memberOf Stack + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ +function stackGet(key) { + return this.__data__.get(key); +} + +var _stackGet = stackGet; + +/** + * Checks if a stack value for `key` exists. + * + * @private + * @name has + * @memberOf Stack + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ +function stackHas(key) { + return this.__data__.has(key); +} + +var _stackHas = stackHas; + +/** Used as the size to enable large array optimizations. */ +var LARGE_ARRAY_SIZE = 200; + +/** + * Sets the stack `key` to `value`. + * + * @private + * @name set + * @memberOf Stack + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the stack cache instance. + */ +function stackSet(key, value) { + var data = this.__data__; + if (data instanceof _ListCache) { + var pairs = data.__data__; + if (!_Map || (pairs.length < LARGE_ARRAY_SIZE - 1)) { + pairs.push([key, value]); + this.size = ++data.size; + return this; + } + data = this.__data__ = new _MapCache(pairs); + } + data.set(key, value); + this.size = data.size; + return this; +} + +var _stackSet = stackSet; + +/** + * Creates a stack cache object to store key-value pairs. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ +function Stack(entries) { + var data = this.__data__ = new _ListCache(entries); + this.size = data.size; +} + +// Add methods to `Stack`. +Stack.prototype.clear = _stackClear; +Stack.prototype['delete'] = _stackDelete; +Stack.prototype.get = _stackGet; +Stack.prototype.has = _stackHas; +Stack.prototype.set = _stackSet; + +var _Stack = Stack; + +/** Used to stand-in for `undefined` hash values. */ +var HASH_UNDEFINED = '__lodash_hash_undefined__'; + +/** + * Adds `value` to the array cache. + * + * @private + * @name add + * @memberOf SetCache + * @alias push + * @param {*} value The value to cache. + * @returns {Object} Returns the cache instance. + */ +function setCacheAdd(value) { + this.__data__.set(value, HASH_UNDEFINED); + return this; +} + +var _setCacheAdd = setCacheAdd; + +/** + * Checks if `value` is in the array cache. + * + * @private + * @name has + * @memberOf SetCache + * @param {*} value The value to search for. + * @returns {boolean} Returns `true` if `value` is found, else `false`. + */ +function setCacheHas(value) { + return this.__data__.has(value); +} + +var _setCacheHas = setCacheHas; + +/** + * + * Creates an array cache object to store unique values. + * + * @private + * @constructor + * @param {Array} [values] The values to cache. + */ +function SetCache(values) { + var index = -1, + length = values == null ? 0 : values.length; + + this.__data__ = new _MapCache; + while (++index < length) { + this.add(values[index]); + } +} + +// Add methods to `SetCache`. +SetCache.prototype.add = SetCache.prototype.push = _setCacheAdd; +SetCache.prototype.has = _setCacheHas; + +var _SetCache = SetCache; + +/** + * A specialized version of `_.some` for arrays without support for iteratee + * shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {boolean} Returns `true` if any element passes the predicate check, + * else `false`. + */ +function arraySome(array, predicate) { + var index = -1, + length = array == null ? 0 : array.length; + + while (++index < length) { + if (predicate(array[index], index, array)) { + return true; + } + } + return false; +} + +var _arraySome = arraySome; + +/** + * Checks if a `cache` value for `key` exists. + * + * @private + * @param {Object} cache The cache to query. + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ +function cacheHas(cache, key) { + return cache.has(key); +} + +var _cacheHas = cacheHas; + +/** Used to compose bitmasks for value comparisons. */ +var COMPARE_PARTIAL_FLAG$5 = 1, + COMPARE_UNORDERED_FLAG$3 = 2; + +/** + * A specialized version of `baseIsEqualDeep` for arrays with support for + * partial deep comparisons. + * + * @private + * @param {Array} array The array to compare. + * @param {Array} other The other array to compare. + * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. + * @param {Function} customizer The function to customize comparisons. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Object} stack Tracks traversed `array` and `other` objects. + * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`. + */ +function equalArrays(array, other, bitmask, customizer, equalFunc, stack) { + var isPartial = bitmask & COMPARE_PARTIAL_FLAG$5, + arrLength = array.length, + othLength = other.length; + + if (arrLength != othLength && !(isPartial && othLength > arrLength)) { + return false; + } + // Check that cyclic values are equal. + var arrStacked = stack.get(array); + var othStacked = stack.get(other); + if (arrStacked && othStacked) { + return arrStacked == other && othStacked == array; + } + var index = -1, + result = true, + seen = (bitmask & COMPARE_UNORDERED_FLAG$3) ? new _SetCache : undefined; + + stack.set(array, other); + stack.set(other, array); + + // Ignore non-index properties. + while (++index < arrLength) { + var arrValue = array[index], + othValue = other[index]; + + if (customizer) { + var compared = isPartial + ? customizer(othValue, arrValue, index, other, array, stack) + : customizer(arrValue, othValue, index, array, other, stack); + } + if (compared !== undefined) { + if (compared) { + continue; + } + result = false; + break; + } + // Recursively compare arrays (susceptible to call stack limits). + if (seen) { + if (!_arraySome(other, function(othValue, othIndex) { + if (!_cacheHas(seen, othIndex) && + (arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) { + return seen.push(othIndex); + } + })) { + result = false; + break; + } + } else if (!( + arrValue === othValue || + equalFunc(arrValue, othValue, bitmask, customizer, stack) + )) { + result = false; + break; + } + } + stack['delete'](array); + stack['delete'](other); + return result; +} + +var _equalArrays = equalArrays; + +/** Built-in value references. */ +var Uint8Array$1 = _root.Uint8Array; + +var _Uint8Array = Uint8Array$1; + +/** + * Converts `map` to its key-value pairs. + * + * @private + * @param {Object} map The map to convert. + * @returns {Array} Returns the key-value pairs. + */ +function mapToArray(map) { + var index = -1, + result = Array(map.size); + + map.forEach(function(value, key) { + result[++index] = [key, value]; + }); + return result; +} + +var _mapToArray = mapToArray; + +/** + * Converts `set` to an array of its values. + * + * @private + * @param {Object} set The set to convert. + * @returns {Array} Returns the values. + */ +function setToArray(set) { + var index = -1, + result = Array(set.size); + + set.forEach(function(value) { + result[++index] = value; + }); + return result; +} + +var _setToArray = setToArray; + +/** Used to compose bitmasks for value comparisons. */ +var COMPARE_PARTIAL_FLAG$4 = 1, + COMPARE_UNORDERED_FLAG$2 = 2; + +/** `Object#toString` result references. */ +var boolTag$1 = '[object Boolean]', + dateTag = '[object Date]', + errorTag = '[object Error]', + mapTag = '[object Map]', + numberTag = '[object Number]', + regexpTag = '[object RegExp]', + setTag = '[object Set]', + stringTag = '[object String]', + symbolTag = '[object Symbol]'; + +var arrayBufferTag = '[object ArrayBuffer]', + dataViewTag = '[object DataView]'; + +/** Used to convert symbols to primitives and strings. */ +var symbolProto = _Symbol ? _Symbol.prototype : undefined, + symbolValueOf = symbolProto ? symbolProto.valueOf : undefined; + +/** + * A specialized version of `baseIsEqualDeep` for comparing objects of + * the same `toStringTag`. + * + * **Note:** This function only supports comparing values with tags of + * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {string} tag The `toStringTag` of the objects to compare. + * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. + * @param {Function} customizer The function to customize comparisons. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Object} stack Tracks traversed `object` and `other` objects. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ +function equalByTag(object, other, tag, bitmask, customizer, equalFunc, stack) { + switch (tag) { + case dataViewTag: + if ((object.byteLength != other.byteLength) || + (object.byteOffset != other.byteOffset)) { + return false; + } + object = object.buffer; + other = other.buffer; + + case arrayBufferTag: + if ((object.byteLength != other.byteLength) || + !equalFunc(new _Uint8Array(object), new _Uint8Array(other))) { + return false; + } + return true; + + case boolTag$1: + case dateTag: + case numberTag: + // Coerce booleans to `1` or `0` and dates to milliseconds. + // Invalid dates are coerced to `NaN`. + return eq_1(+object, +other); + + case errorTag: + return object.name == other.name && object.message == other.message; + + case regexpTag: + case stringTag: + // Coerce regexes to strings and treat strings, primitives and objects, + // as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring + // for more details. + return object == (other + ''); + + case mapTag: + var convert = _mapToArray; + + case setTag: + var isPartial = bitmask & COMPARE_PARTIAL_FLAG$4; + convert || (convert = _setToArray); + + if (object.size != other.size && !isPartial) { + return false; + } + // Assume cyclic values are equal. + var stacked = stack.get(object); + if (stacked) { + return stacked == other; + } + bitmask |= COMPARE_UNORDERED_FLAG$2; + + // Recursively compare objects (susceptible to call stack limits). + stack.set(object, other); + var result = _equalArrays(convert(object), convert(other), bitmask, customizer, equalFunc, stack); + stack['delete'](object); + return result; + + case symbolTag: + if (symbolValueOf) { + return symbolValueOf.call(object) == symbolValueOf.call(other); + } + } + return false; +} + +var _equalByTag = equalByTag; + +/** + * The base implementation of `getAllKeys` and `getAllKeysIn` which uses + * `keysFunc` and `symbolsFunc` to get the enumerable property names and + * symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @param {Function} keysFunc The function to get the keys of `object`. + * @param {Function} symbolsFunc The function to get the symbols of `object`. + * @returns {Array} Returns the array of property names and symbols. + */ +function baseGetAllKeys(object, keysFunc, symbolsFunc) { + var result = keysFunc(object); + return isArray_1(object) ? result : _arrayPush(result, symbolsFunc(object)); +} + +var _baseGetAllKeys = baseGetAllKeys; + +/** + * A specialized version of `_.filter` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {Array} Returns the new filtered array. + */ +function arrayFilter(array, predicate) { + var index = -1, + length = array == null ? 0 : array.length, + resIndex = 0, + result = []; + + while (++index < length) { + var value = array[index]; + if (predicate(value, index, array)) { + result[resIndex++] = value; + } + } + return result; +} + +var _arrayFilter = arrayFilter; + +/** + * This method returns a new empty array. + * + * @static + * @memberOf _ + * @since 4.13.0 + * @category Util + * @returns {Array} Returns the new empty array. + * @example + * + * var arrays = _.times(2, _.stubArray); + * + * console.log(arrays); + * // => [[], []] + * + * console.log(arrays[0] === arrays[1]); + * // => false + */ +function stubArray() { + return []; +} + +var stubArray_1 = stubArray; + +/** Used for built-in method references. */ +var objectProto$3 = Object.prototype; + +/** Built-in value references. */ +var propertyIsEnumerable = objectProto$3.propertyIsEnumerable; + +/* Built-in method references for those with the same name as other `lodash` methods. */ +var nativeGetSymbols = Object.getOwnPropertySymbols; + +/** + * Creates an array of the own enumerable symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of symbols. + */ +var getSymbols = !nativeGetSymbols ? stubArray_1 : function(object) { + if (object == null) { + return []; + } + object = Object(object); + return _arrayFilter(nativeGetSymbols(object), function(symbol) { + return propertyIsEnumerable.call(object, symbol); + }); +}; + +var _getSymbols = getSymbols; + +/** + * The base implementation of `_.times` without support for iteratee shorthands + * or max array length checks. + * + * @private + * @param {number} n The number of times to invoke `iteratee`. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the array of results. + */ +function baseTimes(n, iteratee) { + var index = -1, + result = Array(n); + + while (++index < n) { + result[index] = iteratee(index); + } + return result; +} + +var _baseTimes = baseTimes; + +/** Used as references for various `Number` constants. */ +var MAX_SAFE_INTEGER = 9007199254740991; + +/** Used to detect unsigned integer values. */ +var reIsUint = /^(?:0|[1-9]\d*)$/; + +/** + * Checks if `value` is a valid array-like index. + * + * @private + * @param {*} value The value to check. + * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. + * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. + */ +function isIndex(value, length) { + var type = typeof value; + length = length == null ? MAX_SAFE_INTEGER : length; + + return !!length && + (type == 'number' || + (type != 'symbol' && reIsUint.test(value))) && + (value > -1 && value % 1 == 0 && value < length); +} + +var _isIndex = isIndex; + +/** Used for built-in method references. */ +var objectProto$2 = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty$3 = objectProto$2.hasOwnProperty; + +/** + * Creates an array of the enumerable property names of the array-like `value`. + * + * @private + * @param {*} value The value to query. + * @param {boolean} inherited Specify returning inherited property names. + * @returns {Array} Returns the array of property names. + */ +function arrayLikeKeys(value, inherited) { + var isArr = isArray_1(value), + isArg = !isArr && isArguments_1(value), + isBuff = !isArr && !isArg && isBuffer_1(value), + isType = !isArr && !isArg && !isBuff && isTypedArray_1(value), + skipIndexes = isArr || isArg || isBuff || isType, + result = skipIndexes ? _baseTimes(value.length, String) : [], + length = result.length; + + for (var key in value) { + if ((inherited || hasOwnProperty$3.call(value, key)) && + !(skipIndexes && ( + // Safari 9 has enumerable `arguments.length` in strict mode. + key == 'length' || + // Node.js 0.10 has enumerable non-index properties on buffers. + (isBuff && (key == 'offset' || key == 'parent')) || + // PhantomJS 2 has enumerable non-index properties on typed arrays. + (isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset')) || + // Skip index properties. + _isIndex(key, length) + ))) { + result.push(key); + } + } + return result; +} + +var _arrayLikeKeys = arrayLikeKeys; + +/** + * Creates an array of the own enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. See the + * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) + * for more details. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keys(new Foo); + * // => ['a', 'b'] (iteration order is not guaranteed) + * + * _.keys('hi'); + * // => ['0', '1'] + */ +function keys(object) { + return isArrayLike_1(object) ? _arrayLikeKeys(object) : _baseKeys(object); +} + +var keys_1 = keys; + +/** + * Creates an array of own enumerable property names and symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names and symbols. + */ +function getAllKeys(object) { + return _baseGetAllKeys(object, keys_1, _getSymbols); +} + +var _getAllKeys = getAllKeys; + +/** Used to compose bitmasks for value comparisons. */ +var COMPARE_PARTIAL_FLAG$3 = 1; + +/** Used for built-in method references. */ +var objectProto$1 = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty$2 = objectProto$1.hasOwnProperty; + +/** + * A specialized version of `baseIsEqualDeep` for objects with support for + * partial deep comparisons. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. + * @param {Function} customizer The function to customize comparisons. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Object} stack Tracks traversed `object` and `other` objects. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ +function equalObjects(object, other, bitmask, customizer, equalFunc, stack) { + var isPartial = bitmask & COMPARE_PARTIAL_FLAG$3, + objProps = _getAllKeys(object), + objLength = objProps.length, + othProps = _getAllKeys(other), + othLength = othProps.length; + + if (objLength != othLength && !isPartial) { + return false; + } + var index = objLength; + while (index--) { + var key = objProps[index]; + if (!(isPartial ? key in other : hasOwnProperty$2.call(other, key))) { + return false; + } + } + // Check that cyclic values are equal. + var objStacked = stack.get(object); + var othStacked = stack.get(other); + if (objStacked && othStacked) { + return objStacked == other && othStacked == object; + } + var result = true; + stack.set(object, other); + stack.set(other, object); + + var skipCtor = isPartial; + while (++index < objLength) { + key = objProps[index]; + var objValue = object[key], + othValue = other[key]; + + if (customizer) { + var compared = isPartial + ? customizer(othValue, objValue, key, other, object, stack) + : customizer(objValue, othValue, key, object, other, stack); + } + // Recursively compare objects (susceptible to call stack limits). + if (!(compared === undefined + ? (objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack)) + : compared + )) { + result = false; + break; + } + skipCtor || (skipCtor = key == 'constructor'); + } + if (result && !skipCtor) { + var objCtor = object.constructor, + othCtor = other.constructor; + + // Non `Object` object instances with different constructors are not equal. + if (objCtor != othCtor && + ('constructor' in object && 'constructor' in other) && + !(typeof objCtor == 'function' && objCtor instanceof objCtor && + typeof othCtor == 'function' && othCtor instanceof othCtor)) { + result = false; + } + } + stack['delete'](object); + stack['delete'](other); + return result; +} + +var _equalObjects = equalObjects; + +/** Used to compose bitmasks for value comparisons. */ +var COMPARE_PARTIAL_FLAG$2 = 1; + +/** `Object#toString` result references. */ +var argsTag = '[object Arguments]', + arrayTag = '[object Array]', + objectTag = '[object Object]'; + +/** Used for built-in method references. */ +var objectProto = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty$1 = objectProto.hasOwnProperty; + +/** + * A specialized version of `baseIsEqual` for arrays and objects which performs + * deep comparisons and tracks traversed objects enabling objects with circular + * references to be compared. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. + * @param {Function} customizer The function to customize comparisons. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Object} [stack] Tracks traversed `object` and `other` objects. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ +function baseIsEqualDeep(object, other, bitmask, customizer, equalFunc, stack) { + var objIsArr = isArray_1(object), + othIsArr = isArray_1(other), + objTag = objIsArr ? arrayTag : _getTag(object), + othTag = othIsArr ? arrayTag : _getTag(other); + + objTag = objTag == argsTag ? objectTag : objTag; + othTag = othTag == argsTag ? objectTag : othTag; + + var objIsObj = objTag == objectTag, + othIsObj = othTag == objectTag, + isSameTag = objTag == othTag; + + if (isSameTag && isBuffer_1(object)) { + if (!isBuffer_1(other)) { + return false; + } + objIsArr = true; + objIsObj = false; + } + if (isSameTag && !objIsObj) { + stack || (stack = new _Stack); + return (objIsArr || isTypedArray_1(object)) + ? _equalArrays(object, other, bitmask, customizer, equalFunc, stack) + : _equalByTag(object, other, objTag, bitmask, customizer, equalFunc, stack); + } + if (!(bitmask & COMPARE_PARTIAL_FLAG$2)) { + var objIsWrapped = objIsObj && hasOwnProperty$1.call(object, '__wrapped__'), + othIsWrapped = othIsObj && hasOwnProperty$1.call(other, '__wrapped__'); + + if (objIsWrapped || othIsWrapped) { + var objUnwrapped = objIsWrapped ? object.value() : object, + othUnwrapped = othIsWrapped ? other.value() : other; + + stack || (stack = new _Stack); + return equalFunc(objUnwrapped, othUnwrapped, bitmask, customizer, stack); + } + } + if (!isSameTag) { + return false; + } + stack || (stack = new _Stack); + return _equalObjects(object, other, bitmask, customizer, equalFunc, stack); +} + +var _baseIsEqualDeep = baseIsEqualDeep; + +/** + * The base implementation of `_.isEqual` which supports partial comparisons + * and tracks traversed objects. + * + * @private + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @param {boolean} bitmask The bitmask flags. + * 1 - Unordered comparison + * 2 - Partial comparison + * @param {Function} [customizer] The function to customize comparisons. + * @param {Object} [stack] Tracks traversed `value` and `other` objects. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + */ +function baseIsEqual(value, other, bitmask, customizer, stack) { + if (value === other) { + return true; + } + if (value == null || other == null || (!isObjectLike_1(value) && !isObjectLike_1(other))) { + return value !== value && other !== other; + } + return _baseIsEqualDeep(value, other, bitmask, customizer, baseIsEqual, stack); +} + +var _baseIsEqual = baseIsEqual; + +/** Used to compose bitmasks for value comparisons. */ +var COMPARE_PARTIAL_FLAG$1 = 1, + COMPARE_UNORDERED_FLAG$1 = 2; + +/** + * The base implementation of `_.isMatch` without support for iteratee shorthands. + * + * @private + * @param {Object} object The object to inspect. + * @param {Object} source The object of property values to match. + * @param {Array} matchData The property names, values, and compare flags to match. + * @param {Function} [customizer] The function to customize comparisons. + * @returns {boolean} Returns `true` if `object` is a match, else `false`. + */ +function baseIsMatch(object, source, matchData, customizer) { + var index = matchData.length, + length = index, + noCustomizer = !customizer; + + if (object == null) { + return !length; + } + object = Object(object); + while (index--) { + var data = matchData[index]; + if ((noCustomizer && data[2]) + ? data[1] !== object[data[0]] + : !(data[0] in object) + ) { + return false; + } + } + while (++index < length) { + data = matchData[index]; + var key = data[0], + objValue = object[key], + srcValue = data[1]; + + if (noCustomizer && data[2]) { + if (objValue === undefined && !(key in object)) { + return false; + } + } else { + var stack = new _Stack; + if (customizer) { + var result = customizer(objValue, srcValue, key, object, source, stack); + } + if (!(result === undefined + ? _baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG$1 | COMPARE_UNORDERED_FLAG$1, customizer, stack) + : result + )) { + return false; + } + } + } + return true; +} + +var _baseIsMatch = baseIsMatch; + +/** + * Checks if `value` is suitable for strict equality comparisons, i.e. `===`. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` if suitable for strict + * equality comparisons, else `false`. + */ +function isStrictComparable(value) { + return value === value && !isObject_1(value); +} + +var _isStrictComparable = isStrictComparable; + +/** + * Gets the property names, values, and compare flags of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the match data of `object`. + */ +function getMatchData(object) { + var result = keys_1(object), + length = result.length; + + while (length--) { + var key = result[length], + value = object[key]; + + result[length] = [key, value, _isStrictComparable(value)]; + } + return result; +} + +var _getMatchData = getMatchData; + +/** + * A specialized version of `matchesProperty` for source values suitable + * for strict equality comparisons, i.e. `===`. + * + * @private + * @param {string} key The key of the property to get. + * @param {*} srcValue The value to match. + * @returns {Function} Returns the new spec function. + */ +function matchesStrictComparable(key, srcValue) { + return function(object) { + if (object == null) { + return false; + } + return object[key] === srcValue && + (srcValue !== undefined || (key in Object(object))); + }; +} + +var _matchesStrictComparable = matchesStrictComparable; + +/** + * The base implementation of `_.matches` which doesn't clone `source`. + * + * @private + * @param {Object} source The object of property values to match. + * @returns {Function} Returns the new spec function. + */ +function baseMatches(source) { + var matchData = _getMatchData(source); + if (matchData.length == 1 && matchData[0][2]) { + return _matchesStrictComparable(matchData[0][0], matchData[0][1]); + } + return function(object) { + return object === source || _baseIsMatch(object, source, matchData); + }; +} + +var _baseMatches = baseMatches; + +/** + * Gets the value at `path` of `object`. If the resolved value is + * `undefined`, the `defaultValue` is returned in its place. + * + * @static + * @memberOf _ + * @since 3.7.0 + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path of the property to get. + * @param {*} [defaultValue] The value returned for `undefined` resolved values. + * @returns {*} Returns the resolved value. + * @example + * + * var object = { 'a': [{ 'b': { 'c': 3 } }] }; + * + * _.get(object, 'a[0].b.c'); + * // => 3 + * + * _.get(object, ['a', '0', 'b', 'c']); + * // => 3 + * + * _.get(object, 'a.b.c', 'default'); + * // => 'default' + */ +function get(object, path, defaultValue) { + var result = object == null ? undefined : _baseGet(object, path); + return result === undefined ? defaultValue : result; +} + +var get_1 = get; + +/** + * The base implementation of `_.hasIn` without support for deep paths. + * + * @private + * @param {Object} [object] The object to query. + * @param {Array|string} key The key to check. + * @returns {boolean} Returns `true` if `key` exists, else `false`. + */ +function baseHasIn(object, key) { + return object != null && key in Object(object); +} + +var _baseHasIn = baseHasIn; + +/** + * Checks if `path` exists on `object`. + * + * @private + * @param {Object} object The object to query. + * @param {Array|string} path The path to check. + * @param {Function} hasFunc The function to check properties. + * @returns {boolean} Returns `true` if `path` exists, else `false`. + */ +function hasPath(object, path, hasFunc) { + path = _castPath(path, object); + + var index = -1, + length = path.length, + result = false; + + while (++index < length) { + var key = _toKey(path[index]); + if (!(result = object != null && hasFunc(object, key))) { + break; + } + object = object[key]; + } + if (result || ++index != length) { + return result; + } + length = object == null ? 0 : object.length; + return !!length && isLength_1(length) && _isIndex(key, length) && + (isArray_1(object) || isArguments_1(object)); +} + +var _hasPath = hasPath; + +/** + * Checks if `path` is a direct or inherited property of `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path to check. + * @returns {boolean} Returns `true` if `path` exists, else `false`. + * @example + * + * var object = _.create({ 'a': _.create({ 'b': 2 }) }); + * + * _.hasIn(object, 'a'); + * // => true + * + * _.hasIn(object, 'a.b'); + * // => true + * + * _.hasIn(object, ['a', 'b']); + * // => true + * + * _.hasIn(object, 'b'); + * // => false + */ +function hasIn(object, path) { + return object != null && _hasPath(object, path, _baseHasIn); +} + +var hasIn_1 = hasIn; + +/** Used to compose bitmasks for value comparisons. */ +var COMPARE_PARTIAL_FLAG = 1, + COMPARE_UNORDERED_FLAG = 2; + +/** + * The base implementation of `_.matchesProperty` which doesn't clone `srcValue`. + * + * @private + * @param {string} path The path of the property to get. + * @param {*} srcValue The value to match. + * @returns {Function} Returns the new spec function. + */ +function baseMatchesProperty(path, srcValue) { + if (_isKey(path) && _isStrictComparable(srcValue)) { + return _matchesStrictComparable(_toKey(path), srcValue); + } + return function(object) { + var objValue = get_1(object, path); + return (objValue === undefined && objValue === srcValue) + ? hasIn_1(object, path) + : _baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG); + }; +} + +var _baseMatchesProperty = baseMatchesProperty; + +/** + * This method returns the first argument it receives. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Util + * @param {*} value Any value. + * @returns {*} Returns `value`. + * @example + * + * var object = { 'a': 1 }; + * + * console.log(_.identity(object) === object); + * // => true + */ +function identity(value) { + return value; +} + +var identity_1 = identity; + +/** + * The base implementation of `_.property` without support for deep paths. + * + * @private + * @param {string} key The key of the property to get. + * @returns {Function} Returns the new accessor function. + */ +function baseProperty(key) { + return function(object) { + return object == null ? undefined : object[key]; + }; +} + +var _baseProperty = baseProperty; + +/** + * A specialized version of `baseProperty` which supports deep paths. + * + * @private + * @param {Array|string} path The path of the property to get. + * @returns {Function} Returns the new accessor function. + */ +function basePropertyDeep(path) { + return function(object) { + return _baseGet(object, path); + }; +} + +var _basePropertyDeep = basePropertyDeep; + +/** + * Creates a function that returns the value at `path` of a given object. + * + * @static + * @memberOf _ + * @since 2.4.0 + * @category Util + * @param {Array|string} path The path of the property to get. + * @returns {Function} Returns the new accessor function. + * @example + * + * var objects = [ + * { 'a': { 'b': 2 } }, + * { 'a': { 'b': 1 } } + * ]; + * + * _.map(objects, _.property('a.b')); + * // => [2, 1] + * + * _.map(_.sortBy(objects, _.property(['a', 'b'])), 'a.b'); + * // => [1, 2] + */ +function property(path) { + return _isKey(path) ? _baseProperty(_toKey(path)) : _basePropertyDeep(path); +} + +var property_1 = property; + +/** + * The base implementation of `_.iteratee`. + * + * @private + * @param {*} [value=_.identity] The value to convert to an iteratee. + * @returns {Function} Returns the iteratee. + */ +function baseIteratee(value) { + // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9. + // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details. + if (typeof value == 'function') { + return value; + } + if (value == null) { + return identity_1; + } + if (typeof value == 'object') { + return isArray_1(value) + ? _baseMatchesProperty(value[0], value[1]) + : _baseMatches(value); + } + return property_1(value); +} + +var _baseIteratee = baseIteratee; + +/** + * Creates a base function for methods like `_.forIn` and `_.forOwn`. + * + * @private + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new base function. + */ +function createBaseFor(fromRight) { + return function(object, iteratee, keysFunc) { + var index = -1, + iterable = Object(object), + props = keysFunc(object), + length = props.length; + + while (length--) { + var key = props[fromRight ? length : ++index]; + if (iteratee(iterable[key], key, iterable) === false) { + break; + } + } + return object; + }; +} + +var _createBaseFor = createBaseFor; + +/** + * The base implementation of `baseForOwn` which iterates over `object` + * properties returned by `keysFunc` and invokes `iteratee` for each property. + * Iteratee functions may exit iteration early by explicitly returning `false`. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {Function} keysFunc The function to get the keys of `object`. + * @returns {Object} Returns `object`. + */ +var baseFor = _createBaseFor(); + +var _baseFor = baseFor; + +/** + * The base implementation of `_.forOwn` without support for iteratee shorthands. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Object} Returns `object`. + */ +function baseForOwn(object, iteratee) { + return object && _baseFor(object, iteratee, keys_1); +} + +var _baseForOwn = baseForOwn; + +/** + * Creates a `baseEach` or `baseEachRight` function. + * + * @private + * @param {Function} eachFunc The function to iterate over a collection. + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new base function. + */ +function createBaseEach(eachFunc, fromRight) { + return function(collection, iteratee) { + if (collection == null) { + return collection; + } + if (!isArrayLike_1(collection)) { + return eachFunc(collection, iteratee); + } + var length = collection.length, + index = fromRight ? length : -1, + iterable = Object(collection); + + while ((fromRight ? index-- : ++index < length)) { + if (iteratee(iterable[index], index, iterable) === false) { + break; + } + } + return collection; + }; +} + +var _createBaseEach = createBaseEach; + +/** + * The base implementation of `_.forEach` without support for iteratee shorthands. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array|Object} Returns `collection`. + */ +var baseEach = _createBaseEach(_baseForOwn); + +var _baseEach = baseEach; + +/** + * The base implementation of `_.map` without support for iteratee shorthands. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the new mapped array. + */ +function baseMap(collection, iteratee) { + var index = -1, + result = isArrayLike_1(collection) ? Array(collection.length) : []; + + _baseEach(collection, function(value, key, collection) { + result[++index] = iteratee(value, key, collection); + }); + return result; +} + +var _baseMap = baseMap; + +/** + * The base implementation of `_.sortBy` which uses `comparer` to define the + * sort order of `array` and replaces criteria objects with their corresponding + * values. + * + * @private + * @param {Array} array The array to sort. + * @param {Function} comparer The function to define sort order. + * @returns {Array} Returns `array`. + */ +function baseSortBy(array, comparer) { + var length = array.length; + + array.sort(comparer); + while (length--) { + array[length] = array[length].value; + } + return array; +} + +var _baseSortBy = baseSortBy; + +/** + * Compares values to sort them in ascending order. + * + * @private + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {number} Returns the sort order indicator for `value`. + */ +function compareAscending(value, other) { + if (value !== other) { + var valIsDefined = value !== undefined, + valIsNull = value === null, + valIsReflexive = value === value, + valIsSymbol = isSymbol_1(value); + + var othIsDefined = other !== undefined, + othIsNull = other === null, + othIsReflexive = other === other, + othIsSymbol = isSymbol_1(other); + + if ((!othIsNull && !othIsSymbol && !valIsSymbol && value > other) || + (valIsSymbol && othIsDefined && othIsReflexive && !othIsNull && !othIsSymbol) || + (valIsNull && othIsDefined && othIsReflexive) || + (!valIsDefined && othIsReflexive) || + !valIsReflexive) { + return 1; + } + if ((!valIsNull && !valIsSymbol && !othIsSymbol && value < other) || + (othIsSymbol && valIsDefined && valIsReflexive && !valIsNull && !valIsSymbol) || + (othIsNull && valIsDefined && valIsReflexive) || + (!othIsDefined && valIsReflexive) || + !othIsReflexive) { + return -1; + } + } + return 0; +} + +var _compareAscending = compareAscending; + +/** + * Used by `_.orderBy` to compare multiple properties of a value to another + * and stable sort them. + * + * If `orders` is unspecified, all values are sorted in ascending order. Otherwise, + * specify an order of "desc" for descending or "asc" for ascending sort order + * of corresponding values. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {boolean[]|string[]} orders The order to sort by for each property. + * @returns {number} Returns the sort order indicator for `object`. + */ +function compareMultiple(object, other, orders) { + var index = -1, + objCriteria = object.criteria, + othCriteria = other.criteria, + length = objCriteria.length, + ordersLength = orders.length; + + while (++index < length) { + var result = _compareAscending(objCriteria[index], othCriteria[index]); + if (result) { + if (index >= ordersLength) { + return result; + } + var order = orders[index]; + return result * (order == 'desc' ? -1 : 1); + } + } + // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications + // that causes it, under certain circumstances, to provide the same value for + // `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247 + // for more details. + // + // This also ensures a stable sort in V8 and other engines. + // See https://bugs.chromium.org/p/v8/issues/detail?id=90 for more details. + return object.index - other.index; +} + +var _compareMultiple = compareMultiple; + +/** + * The base implementation of `_.orderBy` without param guards. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by. + * @param {string[]} orders The sort orders of `iteratees`. + * @returns {Array} Returns the new sorted array. + */ +function baseOrderBy(collection, iteratees, orders) { + if (iteratees.length) { + iteratees = _arrayMap(iteratees, function(iteratee) { + if (isArray_1(iteratee)) { + return function(value) { + return _baseGet(value, iteratee.length === 1 ? iteratee[0] : iteratee); + }; + } + return iteratee; + }); + } else { + iteratees = [identity_1]; + } + + var index = -1; + iteratees = _arrayMap(iteratees, _baseUnary(_baseIteratee)); + + var result = _baseMap(collection, function(value, key, collection) { + var criteria = _arrayMap(iteratees, function(iteratee) { + return iteratee(value); + }); + return { 'criteria': criteria, 'index': ++index, 'value': value }; + }); + + return _baseSortBy(result, function(object, other) { + return _compareMultiple(object, other, orders); + }); +} + +var _baseOrderBy = baseOrderBy; + +/** + * A faster alternative to `Function#apply`, this function invokes `func` + * with the `this` binding of `thisArg` and the arguments of `args`. + * + * @private + * @param {Function} func The function to invoke. + * @param {*} thisArg The `this` binding of `func`. + * @param {Array} args The arguments to invoke `func` with. + * @returns {*} Returns the result of `func`. + */ +function apply(func, thisArg, args) { + switch (args.length) { + case 0: return func.call(thisArg); + case 1: return func.call(thisArg, args[0]); + case 2: return func.call(thisArg, args[0], args[1]); + case 3: return func.call(thisArg, args[0], args[1], args[2]); + } + return func.apply(thisArg, args); +} + +var _apply = apply; + +/* Built-in method references for those with the same name as other `lodash` methods. */ +var nativeMax = Math.max; + +/** + * A specialized version of `baseRest` which transforms the rest array. + * + * @private + * @param {Function} func The function to apply a rest parameter to. + * @param {number} [start=func.length-1] The start position of the rest parameter. + * @param {Function} transform The rest array transform. + * @returns {Function} Returns the new function. + */ +function overRest(func, start, transform) { + start = nativeMax(start === undefined ? (func.length - 1) : start, 0); + return function() { + var args = arguments, + index = -1, + length = nativeMax(args.length - start, 0), + array = Array(length); + + while (++index < length) { + array[index] = args[start + index]; + } + index = -1; + var otherArgs = Array(start + 1); + while (++index < start) { + otherArgs[index] = args[index]; + } + otherArgs[start] = transform(array); + return _apply(func, this, otherArgs); + }; +} + +var _overRest = overRest; + +/** + * Creates a function that returns `value`. + * + * @static + * @memberOf _ + * @since 2.4.0 + * @category Util + * @param {*} value The value to return from the new function. + * @returns {Function} Returns the new constant function. + * @example + * + * var objects = _.times(2, _.constant({ 'a': 1 })); + * + * console.log(objects); + * // => [{ 'a': 1 }, { 'a': 1 }] + * + * console.log(objects[0] === objects[1]); + * // => true + */ +function constant(value) { + return function() { + return value; + }; +} + +var constant_1 = constant; + +var defineProperty = (function() { + try { + var func = _getNative(Object, 'defineProperty'); + func({}, '', {}); + return func; + } catch (e) {} +}()); + +var _defineProperty = defineProperty; + +/** + * The base implementation of `setToString` without support for hot loop shorting. + * + * @private + * @param {Function} func The function to modify. + * @param {Function} string The `toString` result. + * @returns {Function} Returns `func`. + */ +var baseSetToString = !_defineProperty ? identity_1 : function(func, string) { + return _defineProperty(func, 'toString', { + 'configurable': true, + 'enumerable': false, + 'value': constant_1(string), + 'writable': true + }); +}; + +var _baseSetToString = baseSetToString; + +/** Used to detect hot functions by number of calls within a span of milliseconds. */ +var HOT_COUNT = 800, + HOT_SPAN = 16; + +/* Built-in method references for those with the same name as other `lodash` methods. */ +var nativeNow = Date.now; + +/** + * Creates a function that'll short out and invoke `identity` instead + * of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN` + * milliseconds. + * + * @private + * @param {Function} func The function to restrict. + * @returns {Function} Returns the new shortable function. + */ +function shortOut(func) { + var count = 0, + lastCalled = 0; + + return function() { + var stamp = nativeNow(), + remaining = HOT_SPAN - (stamp - lastCalled); + + lastCalled = stamp; + if (remaining > 0) { + if (++count >= HOT_COUNT) { + return arguments[0]; + } + } else { + count = 0; + } + return func.apply(undefined, arguments); + }; +} + +var _shortOut = shortOut; + +/** + * Sets the `toString` method of `func` to return `string`. + * + * @private + * @param {Function} func The function to modify. + * @param {Function} string The `toString` result. + * @returns {Function} Returns `func`. + */ +var setToString = _shortOut(_baseSetToString); + +var _setToString = setToString; + +/** + * The base implementation of `_.rest` which doesn't validate or coerce arguments. + * + * @private + * @param {Function} func The function to apply a rest parameter to. + * @param {number} [start=func.length-1] The start position of the rest parameter. + * @returns {Function} Returns the new function. + */ +function baseRest(func, start) { + return _setToString(_overRest(func, start, identity_1), func + ''); +} + +var _baseRest = baseRest; + +/** + * Checks if the given arguments are from an iteratee call. + * + * @private + * @param {*} value The potential iteratee value argument. + * @param {*} index The potential iteratee index or key argument. + * @param {*} object The potential iteratee object argument. + * @returns {boolean} Returns `true` if the arguments are from an iteratee call, + * else `false`. + */ +function isIterateeCall(value, index, object) { + if (!isObject_1(object)) { + return false; + } + var type = typeof index; + if (type == 'number' + ? (isArrayLike_1(object) && _isIndex(index, object.length)) + : (type == 'string' && index in object) + ) { + return eq_1(object[index], value); + } + return false; +} + +var _isIterateeCall = isIterateeCall; + +/** + * Creates an array of elements, sorted in ascending order by the results of + * running each element in a collection thru each iteratee. This method + * performs a stable sort, that is, it preserves the original sort order of + * equal elements. The iteratees are invoked with one argument: (value). + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {...(Function|Function[])} [iteratees=[_.identity]] + * The iteratees to sort by. + * @returns {Array} Returns the new sorted array. + * @example + * + * var users = [ + * { 'user': 'fred', 'age': 48 }, + * { 'user': 'barney', 'age': 36 }, + * { 'user': 'fred', 'age': 30 }, + * { 'user': 'barney', 'age': 34 } + * ]; + * + * _.sortBy(users, [function(o) { return o.user; }]); + * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 30]] + * + * _.sortBy(users, ['user', 'age']); + * // => objects for [['barney', 34], ['barney', 36], ['fred', 30], ['fred', 48]] + */ +var sortBy = _baseRest(function(collection, iteratees) { + if (collection == null) { + return []; + } + var length = iteratees.length; + if (length > 1 && _isIterateeCall(collection, iteratees[0], iteratees[1])) { + iteratees = []; + } else if (length > 2 && _isIterateeCall(iteratees[0], iteratees[1], iteratees[2])) { + iteratees = [iteratees[0]]; + } + return _baseOrderBy(collection, _baseFlatten(iteratees, 1), []); +}); + +var sortBy_1 = sortBy; + +/** + * Configuration for custom action validation rules. + * Defines allowed positions, metadata IDs, data model IDs, and fields for each target + * type. + * + */ +const customActionValidationConfig = { + [CustomActionTarget.LIVEBOARD]: { + positions: [CustomActionsPosition.PRIMARY, CustomActionsPosition.MENU], + allowedMetadataIds: ['liveboardIds'], + allowedDataModelIds: [], + allowedFields: ['name', 'id', 'position', 'target', 'metadataIds', 'orgIds', 'groupIds'], + }, + [CustomActionTarget.VIZ]: { + positions: [CustomActionsPosition.MENU, CustomActionsPosition.PRIMARY, CustomActionsPosition.CONTEXTMENU], + allowedMetadataIds: ['liveboardIds', 'vizIds', 'answerIds'], + allowedDataModelIds: ['modelIds', 'modelColumnNames'], + allowedFields: ['name', 'id', 'position', 'target', 'metadataIds', 'orgIds', 'groupIds', 'dataModelIds'], + }, + [CustomActionTarget.ANSWER]: { + positions: [CustomActionsPosition.MENU, CustomActionsPosition.PRIMARY, CustomActionsPosition.CONTEXTMENU], + allowedMetadataIds: ['answerIds'], + allowedDataModelIds: ['modelIds', 'modelColumnNames'], + allowedFields: ['name', 'id', 'position', 'target', 'metadataIds', 'orgIds', 'groupIds', 'dataModelIds'], + }, + [CustomActionTarget.SPOTTER]: { + positions: [CustomActionsPosition.MENU, CustomActionsPosition.CONTEXTMENU], + allowedMetadataIds: [], + allowedDataModelIds: ['modelIds'], + allowedFields: ['name', 'id', 'position', 'target', 'orgIds', 'groupIds', 'dataModelIds'], + }, +}; +/** + * Validates a single custom action based on its target type + * @param action - The custom action to validate + * @param primaryActionsPerTarget - Map to track primary actions per target + * @returns CustomActionValidation with isValid flag and reason string + * + * @hidden + */ +const validateCustomAction = (action, primaryActionsPerTarget) => { + const { id: actionId, target: targetType, position, metadataIds, dataModelIds } = action; + // Check if target type is supported + if (!customActionValidationConfig[targetType]) { + const errorMessage = CUSTOM_ACTIONS_ERROR_MESSAGE.UNSUPPORTED_TARGET(actionId, targetType); + return { isValid: false, errors: [errorMessage] }; + } + const config = customActionValidationConfig[targetType]; + const errors = []; + // Validate position + if (!arrayIncludesString(config.positions, position)) { + const supportedPositions = config.positions.join(', '); + errors.push(CUSTOM_ACTIONS_ERROR_MESSAGE.INVALID_POSITION(position, targetType, supportedPositions)); + } + // Validate metadata IDs + if (metadataIds) { + const invalidMetadataIds = Object.keys(metadataIds).filter((key) => !arrayIncludesString(config.allowedMetadataIds, key)); + if (invalidMetadataIds.length > 0) { + const supportedMetadataIds = config.allowedMetadataIds.length > 0 ? config.allowedMetadataIds.join(', ') : 'none'; + errors.push(CUSTOM_ACTIONS_ERROR_MESSAGE.INVALID_METADATA_IDS(targetType, invalidMetadataIds, supportedMetadataIds)); + } + } + // Validate data model IDs + if (dataModelIds) { + const invalidDataModelIds = Object.keys(dataModelIds).filter((key) => !arrayIncludesString(config.allowedDataModelIds, key)); + if (invalidDataModelIds.length > 0) { + const supportedDataModelIds = config.allowedDataModelIds.length > 0 ? config.allowedDataModelIds.join(', ') : 'none'; + errors.push(CUSTOM_ACTIONS_ERROR_MESSAGE.INVALID_DATA_MODEL_IDS(targetType, invalidDataModelIds, supportedDataModelIds)); + } + } + // Validate allowed fields + const actionKeys = Object.keys(action); + const invalidFields = actionKeys.filter((key) => !arrayIncludesString(config.allowedFields, key)); + if (invalidFields.length > 0) { + const supportedFields = config.allowedFields.join(', '); + errors.push(CUSTOM_ACTIONS_ERROR_MESSAGE.INVALID_FIELDS(targetType, invalidFields, supportedFields)); + } + return { + isValid: errors.length === 0, + errors, + }; +}; +/** + * Validates basic action structure and required fields + * @param action - The action to validate + * @returns Object containing validation result and missing fields + * + * @hidden + */ +const validateActionStructure = (action) => { + if (!action || typeof action !== 'object') { + return { isValid: false, missingFields: [] }; + } + // Check for all missing required fields + const missingFields = ['id', 'name', 'target', 'position'].filter(field => !action[field]); + return { isValid: missingFields.length === 0, missingFields }; +}; +/** + * Checks for duplicate IDs among actions + * @param actions - Array of actions to check + * @returns Object containing filtered actions and duplicate errors + * + * @hidden + */ +const filterDuplicateIds = (actions) => { + const idMap = actions.reduce((map, action) => { + const list = map.get(action.id) || []; + list.push(action); + map.set(action.id, list); + return map; + }, new Map()); + const { actions: actionsWithUniqueIds, errors } = Array.from(idMap.entries()).reduce((acc, [id, actionsWithSameId]) => { + if (actionsWithSameId.length === 1) { + acc.actions.push(actionsWithSameId[0]); + } + else { + // Keep the first action and add error for duplicates + acc.actions.push(actionsWithSameId[0]); + const duplicateNames = actionsWithSameId.slice(1).map(action => action.name); + acc.errors.push(CUSTOM_ACTIONS_ERROR_MESSAGE.DUPLICATE_IDS(id, duplicateNames, actionsWithSameId[0].name)); + } + return acc; + }, { actions: [], errors: [] }); + return { actions: actionsWithUniqueIds, errors }; +}; +/** + * Validates and processes custom actions + * @param customActions - Array of custom actions to validate + * @returns Object containing valid actions and any validation errors + */ +const getCustomActions = (customActions) => { + const errors = []; + if (!customActions || !Array.isArray(customActions)) { + return { actions: [], errors: [] }; + } + // Step 1: Handle invalid actions first (null, undefined, missing required + // fields) + const validActions = customActions.filter(action => { + const validation = validateActionStructure(action); + if (!validation.isValid) { + if (!action || typeof action !== 'object') { + errors.push(CUSTOM_ACTIONS_ERROR_MESSAGE.INVALID_ACTION_OBJECT); + } + else { + errors.push(CUSTOM_ACTIONS_ERROR_MESSAGE.MISSING_REQUIRED_FIELDS(action.id, validation.missingFields)); + } + return false; + } + return true; + }); + // Step 2: Check for duplicate IDs among valid actions + const { actions: actionsWithUniqueIds, errors: duplicateErrors } = filterDuplicateIds(validActions); + // Add duplicate errors to the errors array + duplicateErrors.forEach(error => errors.push(error)); + // Step 3: Validate actions with unique IDs + const finalValidActions = []; + actionsWithUniqueIds.forEach((action) => { + const { isValid, errors: validationErrors } = validateCustomAction(action); + validationErrors.forEach(error => errors.push(error)); + if (isValid) { + finalValidActions.push(action); + } + }); + // Step 4: Collect warnings for long custom action names + const MAX_ACTION_NAME_LENGTH = 30; + const warnings = finalValidActions + .filter(action => action.name.length > MAX_ACTION_NAME_LENGTH) + .map(action => `Custom action name '${action.name}' exceeds ${MAX_ACTION_NAME_LENGTH} characters. This may cause display or truncation issues in the UI.`); + if (warnings.length > 0) { + logger$3.warn(warnings); + } + const sortedActions = sortBy_1(finalValidActions, (a) => a.name.toLocaleLowerCase()); + return { + actions: sortedActions, + errors: errors, + }; +}; + +/** + * Copyright (c) 2023 + * + * Utilities related to reading configuration objects + * @summary Config-related utils + * @author Ayon Ghosh + */ +const urlRegex = new RegExp([ + '(^(https?:)//)?', + '(([^:/?#]*)(?::([0-9]+))?)', + '(/{0,1}[^?#]*)', + '(\\?[^#]*|)', + '(#.*|)$', // hash +].join('')); +/** + * Parse and construct the ThoughtSpot hostname or IP address + * from the embed configuration object. + * @param config + */ +const getThoughtSpotHost = (config) => { + if (!config.thoughtSpotHost) { + throw new Error(ERROR_MESSAGE.INVALID_THOUGHTSPOT_HOST); + } + const urlParts = config.thoughtSpotHost.match(urlRegex); + if (!urlParts) { + throw new Error(ERROR_MESSAGE.INVALID_THOUGHTSPOT_HOST); + } + const protocol = urlParts[2] || window.location.protocol; + const host = urlParts[3]; + let path = urlParts[6]; + // Lose the trailing / if any + if (path.charAt(path.length - 1) === '/') { + path = path.substring(0, path.length - 1); + } + // const urlParams = urlParts[7]; + // const hash = urlParts[8]; + return `${protocol}//${host}${path}`; +}; +const getV2BasePath = (config) => { + if (config.basepath) { + return config.basepath; + } + const tsHost = getThoughtSpotHost(config); + // This is to handle when e2e's. Search is run on pods for + // comp-blink-test-pipeline with baseUrl=https://localhost:8443. + // This is to handle when the developer is developing in their local + // environment. + if (tsHost.includes('://localhost') && !tsHost.includes(':8443')) { + return ''; + } + return 'v2'; +}; +/** + * It is a good idea to keep URLs under 2000 chars. + * If this is ever breached, since we pass view configuration through + * URL params, we would like to log a warning. + * Reference: https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers + */ +const URL_MAX_LENGTH = 2000; +/** + * The default CSS dimensions of the embedded app + */ +const DEFAULT_EMBED_WIDTH = '100%'; +const DEFAULT_EMBED_HEIGHT = '100%'; + +var Config = { + DEBUG: false, + LIB_VERSION: '2.47.0' +}; + +// since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file +var window$1; +if (typeof(window) === 'undefined') { + var loc = { + hostname: '' + }; + window$1 = { + navigator: { userAgent: '' }, + document: { + location: loc, + referrer: '' + }, + screen: { width: 0, height: 0 }, + location: loc + }; +} else { + window$1 = window; +} + +/* + * Saved references to long variable names, so that closure compiler can + * minimize file size. + */ + +var ArrayProto = Array.prototype; +var FuncProto = Function.prototype; +var ObjProto = Object.prototype; +var slice = ArrayProto.slice; +var toString = ObjProto.toString; +var hasOwnProperty = ObjProto.hasOwnProperty; +var windowConsole = window$1.console; +var navigator = window$1.navigator; +var document$1 = window$1.document; +var windowOpera = window$1.opera; +var screen = window$1.screen; +var userAgent = navigator.userAgent; +var nativeBind = FuncProto.bind; +var nativeForEach = ArrayProto.forEach; +var nativeIndexOf = ArrayProto.indexOf; +var nativeMap = ArrayProto.map; +var nativeIsArray = Array.isArray; +var breaker = {}; +var _ = { + trim: function(str) { + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Polyfill + return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); + } +}; + +// Console override +var console$1 = { + /** @type {function(...*)} */ + log: function() { + if (Config.DEBUG && !_.isUndefined(windowConsole) && windowConsole) { + try { + windowConsole.log.apply(windowConsole, arguments); + } catch (err) { + _.each(arguments, function(arg) { + windowConsole.log(arg); + }); + } + } + }, + /** @type {function(...*)} */ + warn: function() { + if (Config.DEBUG && !_.isUndefined(windowConsole) && windowConsole) { + var args = ['Mixpanel warning:'].concat(_.toArray(arguments)); + try { + windowConsole.warn.apply(windowConsole, args); + } catch (err) { + _.each(args, function(arg) { + windowConsole.warn(arg); + }); + } + } + }, + /** @type {function(...*)} */ + error: function() { + if (Config.DEBUG && !_.isUndefined(windowConsole) && windowConsole) { + var args = ['Mixpanel error:'].concat(_.toArray(arguments)); + try { + windowConsole.error.apply(windowConsole, args); + } catch (err) { + _.each(args, function(arg) { + windowConsole.error(arg); + }); + } + } + }, + /** @type {function(...*)} */ + critical: function() { + if (!_.isUndefined(windowConsole) && windowConsole) { + var args = ['Mixpanel error:'].concat(_.toArray(arguments)); + try { + windowConsole.error.apply(windowConsole, args); + } catch (err) { + _.each(args, function(arg) { + windowConsole.error(arg); + }); + } + } + } +}; + +var log_func_with_prefix = function(func, prefix) { + return function() { + arguments[0] = '[' + prefix + '] ' + arguments[0]; + return func.apply(console$1, arguments); + }; +}; +var console_with_prefix = function(prefix) { + return { + log: log_func_with_prefix(console$1.log, prefix), + error: log_func_with_prefix(console$1.error, prefix), + critical: log_func_with_prefix(console$1.critical, prefix) + }; +}; + + +// UNDERSCORE +// Embed part of the Underscore Library +_.bind = function(func, context) { + var args, bound; + if (nativeBind && func.bind === nativeBind) { + return nativeBind.apply(func, slice.call(arguments, 1)); + } + if (!_.isFunction(func)) { + throw new TypeError(); + } + args = slice.call(arguments, 2); + bound = function() { + if (!(this instanceof bound)) { + return func.apply(context, args.concat(slice.call(arguments))); + } + var ctor = {}; + ctor.prototype = func.prototype; + var self = new ctor(); + ctor.prototype = null; + var result = func.apply(self, args.concat(slice.call(arguments))); + if (Object(result) === result) { + return result; + } + return self; + }; + return bound; +}; + +/** + * @param {*=} obj + * @param {function(...*)=} iterator + * @param {Object=} context + */ +_.each = function(obj, iterator, context) { + if (obj === null || obj === undefined) { + return; + } + if (nativeForEach && obj.forEach === nativeForEach) { + obj.forEach(iterator, context); + } else if (obj.length === +obj.length) { + for (var i = 0, l = obj.length; i < l; i++) { + if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) { + return; + } + } + } else { + for (var key in obj) { + if (hasOwnProperty.call(obj, key)) { + if (iterator.call(context, obj[key], key, obj) === breaker) { + return; + } + } + } + } +}; + +_.extend = function(obj) { + _.each(slice.call(arguments, 1), function(source) { + for (var prop in source) { + if (source[prop] !== void 0) { + obj[prop] = source[prop]; + } + } + }); + return obj; +}; + +_.isArray = nativeIsArray || function(obj) { + return toString.call(obj) === '[object Array]'; +}; + +// from a comment on http://dbj.org/dbj/?p=286 +// fails on only one very rare and deliberate custom object: +// var bomb = { toString : undefined, valueOf: function(o) { return "function BOMBA!"; }}; +_.isFunction = function(f) { + try { + return /^\s*\bfunction\b/.test(f); + } catch (x) { + return false; + } +}; + +_.isArguments = function(obj) { + return !!(obj && hasOwnProperty.call(obj, 'callee')); +}; + +_.toArray = function(iterable) { + if (!iterable) { + return []; + } + if (iterable.toArray) { + return iterable.toArray(); + } + if (_.isArray(iterable)) { + return slice.call(iterable); + } + if (_.isArguments(iterable)) { + return slice.call(iterable); + } + return _.values(iterable); +}; + +_.map = function(arr, callback, context) { + if (nativeMap && arr.map === nativeMap) { + return arr.map(callback, context); + } else { + var results = []; + _.each(arr, function(item) { + results.push(callback.call(context, item)); + }); + return results; + } +}; + +_.keys = function(obj) { + var results = []; + if (obj === null) { + return results; + } + _.each(obj, function(value, key) { + results[results.length] = key; + }); + return results; +}; + +_.values = function(obj) { + var results = []; + if (obj === null) { + return results; + } + _.each(obj, function(value) { + results[results.length] = value; + }); + return results; +}; + +_.include = function(obj, target) { + var found = false; + if (obj === null) { + return found; + } + if (nativeIndexOf && obj.indexOf === nativeIndexOf) { + return obj.indexOf(target) != -1; + } + _.each(obj, function(value) { + if (found || (found = (value === target))) { + return breaker; + } + }); + return found; +}; + +_.includes = function(str, needle) { + return str.indexOf(needle) !== -1; +}; + +// Underscore Addons +_.inherit = function(subclass, superclass) { + subclass.prototype = new superclass(); + subclass.prototype.constructor = subclass; + subclass.superclass = superclass.prototype; + return subclass; +}; + +_.isObject = function(obj) { + return (obj === Object(obj) && !_.isArray(obj)); +}; + +_.isEmptyObject = function(obj) { + if (_.isObject(obj)) { + for (var key in obj) { + if (hasOwnProperty.call(obj, key)) { + return false; + } + } + return true; + } + return false; +}; + +_.isUndefined = function(obj) { + return obj === void 0; +}; + +_.isString = function(obj) { + return toString.call(obj) == '[object String]'; +}; + +_.isDate = function(obj) { + return toString.call(obj) == '[object Date]'; +}; + +_.isNumber = function(obj) { + return toString.call(obj) == '[object Number]'; +}; + +_.isElement = function(obj) { + return !!(obj && obj.nodeType === 1); +}; + +_.encodeDates = function(obj) { + _.each(obj, function(v, k) { + if (_.isDate(v)) { + obj[k] = _.formatDate(v); + } else if (_.isObject(v)) { + obj[k] = _.encodeDates(v); // recurse + } + }); + return obj; +}; + +_.timestamp = function() { + Date.now = Date.now || function() { + return +new Date; + }; + return Date.now(); +}; + +_.formatDate = function(d) { + // YYYY-MM-DDTHH:MM:SS in UTC + function pad(n) { + return n < 10 ? '0' + n : n; + } + return d.getUTCFullYear() + '-' + + pad(d.getUTCMonth() + 1) + '-' + + pad(d.getUTCDate()) + 'T' + + pad(d.getUTCHours()) + ':' + + pad(d.getUTCMinutes()) + ':' + + pad(d.getUTCSeconds()); +}; + +_.strip_empty_properties = function(p) { + var ret = {}; + _.each(p, function(v, k) { + if (_.isString(v) && v.length > 0) { + ret[k] = v; + } + }); + return ret; +}; + +/* + * this function returns a copy of object after truncating it. If + * passed an Array or Object it will iterate through obj and + * truncate all the values recursively. + */ +_.truncate = function(obj, length) { + var ret; + + if (typeof(obj) === 'string') { + ret = obj.slice(0, length); + } else if (_.isArray(obj)) { + ret = []; + _.each(obj, function(val) { + ret.push(_.truncate(val, length)); + }); + } else if (_.isObject(obj)) { + ret = {}; + _.each(obj, function(val, key) { + ret[key] = _.truncate(val, length); + }); + } else { + ret = obj; + } + + return ret; +}; + +_.JSONEncode = (function() { + return function(mixed_val) { + var value = mixed_val; + var quote = function(string) { + var escapable = /[\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; // eslint-disable-line no-control-regex + var meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"': '\\"', + '\\': '\\\\' + }; + + escapable.lastIndex = 0; + return escapable.test(string) ? + '"' + string.replace(escapable, function(a) { + var c = meta[a]; + return typeof c === 'string' ? c : + '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : + '"' + string + '"'; + }; + + var str = function(key, holder) { + var gap = ''; + var indent = ' '; + var i = 0; // The loop counter. + var k = ''; // The member key. + var v = ''; // The member value. + var length = 0; + var mind = gap; + var partial = []; + var value = holder[key]; + + // If the value has a toJSON method, call it to obtain a replacement value. + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + + // What happens next depends on the value's type. + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + // JSON numbers must be finite. Encode non-finite numbers as null. + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + // If the value is a boolean or null, convert it to a string. Note: + // typeof null does not produce 'null'. The case is included here in + // the remote chance that this gets fixed someday. + + return String(value); + + case 'object': + // If the type is 'object', we might be dealing with an object or an array or + // null. + // Due to a specification blunder in ECMAScript, typeof null is 'object', + // so watch out for that case. + if (!value) { + return 'null'; + } + + // Make an array to hold the partial results of stringifying this object value. + gap += indent; + partial = []; + + // Is the value an array? + if (toString.apply(value) === '[object Array]') { + // The value is an array. Stringify every element. Use null as a placeholder + // for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + + // Join all of the elements together, separated with commas, and wrap them in + // brackets. + v = partial.length === 0 ? '[]' : + gap ? '[\n' + gap + + partial.join(',\n' + gap) + '\n' + + mind + ']' : + '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + + // Iterate through all of the keys in the object. + for (k in value) { + if (hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + + // Join all of the member texts together, separated with commas, + // and wrap them in braces. + v = partial.length === 0 ? '{}' : + gap ? '{' + partial.join(',') + '' + + mind + '}' : '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + }; + + // Make a fake root object containing our value under the key of ''. + // Return the result of stringifying the value. + return str('', { + '': value + }); + }; +})(); + +/** + * From https://github.com/douglascrockford/JSON-js/blob/master/json_parse.js + * Slightly modified to throw a real Error rather than a POJO + */ +_.JSONDecode = (function() { + var at, // The index of the current character + ch, // The current character + escapee = { + '"': '"', + '\\': '\\', + '/': '/', + 'b': '\b', + 'f': '\f', + 'n': '\n', + 'r': '\r', + 't': '\t' + }, + text, + error = function(m) { + var e = new SyntaxError(m); + e.at = at; + e.text = text; + throw e; + }, + next = function(c) { + // If a c parameter is provided, verify that it matches the current character. + if (c && c !== ch) { + error('Expected \'' + c + '\' instead of \'' + ch + '\''); + } + // Get the next character. When there are no more characters, + // return the empty string. + ch = text.charAt(at); + at += 1; + return ch; + }, + number = function() { + // Parse a number value. + var number, + string = ''; + + if (ch === '-') { + string = '-'; + next('-'); + } + while (ch >= '0' && ch <= '9') { + string += ch; + next(); + } + if (ch === '.') { + string += '.'; + while (next() && ch >= '0' && ch <= '9') { + string += ch; + } + } + if (ch === 'e' || ch === 'E') { + string += ch; + next(); + if (ch === '-' || ch === '+') { + string += ch; + next(); + } + while (ch >= '0' && ch <= '9') { + string += ch; + next(); + } + } + number = +string; + if (!isFinite(number)) { + error('Bad number'); + } else { + return number; + } + }, + + string = function() { + // Parse a string value. + var hex, + i, + string = '', + uffff; + // When parsing for string values, we must look for " and \ characters. + if (ch === '"') { + while (next()) { + if (ch === '"') { + next(); + return string; + } + if (ch === '\\') { + next(); + if (ch === 'u') { + uffff = 0; + for (i = 0; i < 4; i += 1) { + hex = parseInt(next(), 16); + if (!isFinite(hex)) { + break; + } + uffff = uffff * 16 + hex; + } + string += String.fromCharCode(uffff); + } else if (typeof escapee[ch] === 'string') { + string += escapee[ch]; + } else { + break; + } + } else { + string += ch; + } + } + } + error('Bad string'); + }, + white = function() { + // Skip whitespace. + while (ch && ch <= ' ') { + next(); + } + }, + word = function() { + // true, false, or null. + switch (ch) { + case 't': + next('t'); + next('r'); + next('u'); + next('e'); + return true; + case 'f': + next('f'); + next('a'); + next('l'); + next('s'); + next('e'); + return false; + case 'n': + next('n'); + next('u'); + next('l'); + next('l'); + return null; + } + error('Unexpected "' + ch + '"'); + }, + value, // Placeholder for the value function. + array = function() { + // Parse an array value. + var array = []; + + if (ch === '[') { + next('['); + white(); + if (ch === ']') { + next(']'); + return array; // empty array + } + while (ch) { + array.push(value()); + white(); + if (ch === ']') { + next(']'); + return array; + } + next(','); + white(); + } + } + error('Bad array'); + }, + object = function() { + // Parse an object value. + var key, + object = {}; + + if (ch === '{') { + next('{'); + white(); + if (ch === '}') { + next('}'); + return object; // empty object + } + while (ch) { + key = string(); + white(); + next(':'); + if (Object.hasOwnProperty.call(object, key)) { + error('Duplicate key "' + key + '"'); + } + object[key] = value(); + white(); + if (ch === '}') { + next('}'); + return object; + } + next(','); + white(); + } + } + error('Bad object'); + }; + + value = function() { + // Parse a JSON value. It could be an object, an array, a string, + // a number, or a word. + white(); + switch (ch) { + case '{': + return object(); + case '[': + return array(); + case '"': + return string(); + case '-': + return number(); + default: + return ch >= '0' && ch <= '9' ? number() : word(); + } + }; + + // Return the json_parse function. It will have access to all of the + // above functions and variables. + return function(source) { + var result; + + text = source; + at = 0; + ch = ' '; + result = value(); + white(); + if (ch) { + error('Syntax error'); + } + + return result; + }; +})(); + +_.base64Encode = function(data) { + var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, + ac = 0, + enc = '', + tmp_arr = []; + + if (!data) { + return data; + } + + data = _.utf8Encode(data); + + do { // pack three octets into four hexets + o1 = data.charCodeAt(i++); + o2 = data.charCodeAt(i++); + o3 = data.charCodeAt(i++); + + bits = o1 << 16 | o2 << 8 | o3; + + h1 = bits >> 18 & 0x3f; + h2 = bits >> 12 & 0x3f; + h3 = bits >> 6 & 0x3f; + h4 = bits & 0x3f; + + // use hexets to index into b64, and append result to encoded string + tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4); + } while (i < data.length); + + enc = tmp_arr.join(''); + + switch (data.length % 3) { + case 1: + enc = enc.slice(0, -2) + '=='; + break; + case 2: + enc = enc.slice(0, -1) + '='; + break; + } + + return enc; +}; + +_.utf8Encode = function(string) { + string = (string + '').replace(/\r\n/g, '\n').replace(/\r/g, '\n'); + + var utftext = '', + start, + end; + var stringl = 0, + n; + + start = end = 0; + stringl = string.length; + + for (n = 0; n < stringl; n++) { + var c1 = string.charCodeAt(n); + var enc = null; + + if (c1 < 128) { + end++; + } else if ((c1 > 127) && (c1 < 2048)) { + enc = String.fromCharCode((c1 >> 6) | 192, (c1 & 63) | 128); + } else { + enc = String.fromCharCode((c1 >> 12) | 224, ((c1 >> 6) & 63) | 128, (c1 & 63) | 128); + } + if (enc !== null) { + if (end > start) { + utftext += string.substring(start, end); + } + utftext += enc; + start = end = n + 1; + } + } + + if (end > start) { + utftext += string.substring(start, string.length); + } + + return utftext; +}; + +_.UUID = (function() { + + // Time-based entropy + var T = function() { + var time = 1 * new Date(); // cross-browser version of Date.now() + var ticks; + if (window$1.performance && window$1.performance.now) { + ticks = window$1.performance.now(); + } else { + // fall back to busy loop + ticks = 0; + + // this while loop figures how many browser ticks go by + // before 1*new Date() returns a new number, ie the amount + // of ticks that go by per millisecond + while (time == 1 * new Date()) { + ticks++; + } + } + return time.toString(16) + Math.floor(ticks).toString(16); + }; + + // Math.Random entropy + var R = function() { + return Math.random().toString(16).replace('.', ''); + }; + + // User agent entropy + // This function takes the user agent string, and then xors + // together each sequence of 8 bytes. This produces a final + // sequence of 8 bytes which it returns as hex. + var UA = function() { + var ua = userAgent, + i, ch, buffer = [], + ret = 0; + + function xor(result, byte_array) { + var j, tmp = 0; + for (j = 0; j < byte_array.length; j++) { + tmp |= (buffer[j] << j * 8); + } + return result ^ tmp; + } + + for (i = 0; i < ua.length; i++) { + ch = ua.charCodeAt(i); + buffer.unshift(ch & 0xFF); + if (buffer.length >= 4) { + ret = xor(ret, buffer); + buffer = []; + } + } + + if (buffer.length > 0) { + ret = xor(ret, buffer); + } + + return ret.toString(16); + }; + + return function() { + var se = (screen.height * screen.width).toString(16); + return (T() + '-' + R() + '-' + UA() + '-' + se + '-' + T()); + }; +})(); + +// _.isBlockedUA() +// This is to block various web spiders from executing our JS and +// sending false tracking data +var BLOCKED_UA_STRS = [ + 'ahrefsbot', + 'baiduspider', + 'bingbot', + 'bingpreview', + 'facebookexternal', + 'petalbot', + 'pinterest', + 'screaming frog', + 'yahoo! slurp', + 'yandexbot', + + // a whole bunch of goog-specific crawlers + // https://developers.google.com/search/docs/advanced/crawling/overview-google-crawlers + 'adsbot-google', + 'apis-google', + 'duplexweb-google', + 'feedfetcher-google', + 'google favicon', + 'google web preview', + 'google-read-aloud', + 'googlebot', + 'googleweblight', + 'mediapartners-google', + 'storebot-google' +]; +_.isBlockedUA = function(ua) { + var i; + ua = ua.toLowerCase(); + for (i = 0; i < BLOCKED_UA_STRS.length; i++) { + if (ua.indexOf(BLOCKED_UA_STRS[i]) !== -1) { + return true; + } + } + return false; +}; + +/** + * @param {Object=} formdata + * @param {string=} arg_separator + */ +_.HTTPBuildQuery = function(formdata, arg_separator) { + var use_val, use_key, tmp_arr = []; + + if (_.isUndefined(arg_separator)) { + arg_separator = '&'; + } + + _.each(formdata, function(val, key) { + use_val = encodeURIComponent(val.toString()); + use_key = encodeURIComponent(key); + tmp_arr[tmp_arr.length] = use_key + '=' + use_val; + }); + + return tmp_arr.join(arg_separator); +}; + +_.getQueryParam = function(url, param) { + // Expects a raw URL + + param = param.replace(/[[]/, '\\[').replace(/[\]]/, '\\]'); + var regexS = '[\\?&]' + param + '=([^&#]*)', + regex = new RegExp(regexS), + results = regex.exec(url); + if (results === null || (results && typeof(results[1]) !== 'string' && results[1].length)) { + return ''; + } else { + var result = results[1]; + try { + result = decodeURIComponent(result); + } catch(err) { + console$1.error('Skipping decoding for malformed query param: ' + result); + } + return result.replace(/\+/g, ' '); + } +}; + + +// _.cookie +// Methods partially borrowed from quirksmode.org/js/cookies.html +_.cookie = { + get: function(name) { + var nameEQ = name + '='; + var ca = document$1.cookie.split(';'); + for (var i = 0; i < ca.length; i++) { + var c = ca[i]; + while (c.charAt(0) == ' ') { + c = c.substring(1, c.length); + } + if (c.indexOf(nameEQ) === 0) { + return decodeURIComponent(c.substring(nameEQ.length, c.length)); + } + } + return null; + }, + + parse: function(name) { + var cookie; + try { + cookie = _.JSONDecode(_.cookie.get(name)) || {}; + } catch (err) { + // noop + } + return cookie; + }, + + set_seconds: function(name, value, seconds, is_cross_subdomain, is_secure, is_cross_site, domain_override) { + var cdomain = '', + expires = '', + secure = ''; + + if (domain_override) { + cdomain = '; domain=' + domain_override; + } else if (is_cross_subdomain) { + var domain = extract_domain(document$1.location.hostname); + cdomain = domain ? '; domain=.' + domain : ''; + } + + if (seconds) { + var date = new Date(); + date.setTime(date.getTime() + (seconds * 1000)); + expires = '; expires=' + date.toGMTString(); + } + + if (is_cross_site) { + is_secure = true; + secure = '; SameSite=None'; + } + if (is_secure) { + secure += '; secure'; + } + + document$1.cookie = name + '=' + encodeURIComponent(value) + expires + '; path=/' + cdomain + secure; + }, + + set: function(name, value, days, is_cross_subdomain, is_secure, is_cross_site, domain_override) { + var cdomain = '', expires = '', secure = ''; + + if (domain_override) { + cdomain = '; domain=' + domain_override; + } else if (is_cross_subdomain) { + var domain = extract_domain(document$1.location.hostname); + cdomain = domain ? '; domain=.' + domain : ''; + } + + if (days) { + var date = new Date(); + date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + expires = '; expires=' + date.toGMTString(); + } + + if (is_cross_site) { + is_secure = true; + secure = '; SameSite=None'; + } + if (is_secure) { + secure += '; secure'; + } + + var new_cookie_val = name + '=' + encodeURIComponent(value) + expires + '; path=/' + cdomain + secure; + document$1.cookie = new_cookie_val; + return new_cookie_val; + }, + + remove: function(name, is_cross_subdomain, domain_override) { + _.cookie.set(name, '', -1, is_cross_subdomain, false, false, domain_override); + } +}; + +var _localStorageSupported = null; +var localStorageSupported = function(storage, forceCheck) { + if (_localStorageSupported !== null && !forceCheck) { + return _localStorageSupported; + } + + var supported = true; + try { + storage = storage || window.localStorage; + var key = '__mplss_' + cheap_guid(8), + val = 'xyz'; + storage.setItem(key, val); + if (storage.getItem(key) !== val) { + supported = false; + } + storage.removeItem(key); + } catch (err) { + supported = false; + } + + _localStorageSupported = supported; + return supported; +}; + +// _.localStorage +_.localStorage = { + is_supported: function(force_check) { + var supported = localStorageSupported(null, force_check); + if (!supported) { + console$1.error('localStorage unsupported; falling back to cookie store'); + } + return supported; + }, + + error: function(msg) { + console$1.error('localStorage error: ' + msg); + }, + + get: function(name) { + try { + return window.localStorage.getItem(name); + } catch (err) { + _.localStorage.error(err); + } + return null; + }, + + parse: function(name) { + try { + return _.JSONDecode(_.localStorage.get(name)) || {}; + } catch (err) { + // noop + } + return null; + }, + + set: function(name, value) { + try { + window.localStorage.setItem(name, value); + } catch (err) { + _.localStorage.error(err); + } + }, + + remove: function(name) { + try { + window.localStorage.removeItem(name); + } catch (err) { + _.localStorage.error(err); + } + } +}; + +_.register_event = (function() { + // written by Dean Edwards, 2005 + // with input from Tino Zijdel - crisp@xs4all.nl + // with input from Carl Sverre - mail@carlsverre.com + // with input from Mixpanel + // http://dean.edwards.name/weblog/2005/10/add-event/ + // https://gist.github.com/1930440 + + /** + * @param {Object} element + * @param {string} type + * @param {function(...*)} handler + * @param {boolean=} oldSchool + * @param {boolean=} useCapture + */ + var register_event = function(element, type, handler, oldSchool, useCapture) { + if (!element) { + console$1.error('No valid element provided to register_event'); + return; + } + + if (element.addEventListener && !oldSchool) { + element.addEventListener(type, handler, !!useCapture); + } else { + var ontype = 'on' + type; + var old_handler = element[ontype]; // can be undefined + element[ontype] = makeHandler(element, handler, old_handler); + } + }; + + function makeHandler(element, new_handler, old_handlers) { + var handler = function(event) { + event = event || fixEvent(window.event); + + // this basically happens in firefox whenever another script + // overwrites the onload callback and doesn't pass the event + // object to previously defined callbacks. All the browsers + // that don't define window.event implement addEventListener + // so the dom_loaded handler will still be fired as usual. + if (!event) { + return undefined; + } + + var ret = true; + var old_result, new_result; + + if (_.isFunction(old_handlers)) { + old_result = old_handlers(event); + } + new_result = new_handler.call(element, event); + + if ((false === old_result) || (false === new_result)) { + ret = false; + } + + return ret; + }; + + return handler; + } + + function fixEvent(event) { + if (event) { + event.preventDefault = fixEvent.preventDefault; + event.stopPropagation = fixEvent.stopPropagation; + } + return event; + } + fixEvent.preventDefault = function() { + this.returnValue = false; + }; + fixEvent.stopPropagation = function() { + this.cancelBubble = true; + }; + + return register_event; +})(); + + +var TOKEN_MATCH_REGEX = new RegExp('^(\\w*)\\[(\\w+)([=~\\|\\^\\$\\*]?)=?"?([^\\]"]*)"?\\]$'); + +_.dom_query = (function() { + /* document.getElementsBySelector(selector) + - returns an array of element objects from the current document + matching the CSS selector. Selectors can contain element names, + class names and ids and can be nested. For example: + + elements = document.getElementsBySelector('div#main p a.external') + + Will return an array of all 'a' elements with 'external' in their + class attribute that are contained inside 'p' elements that are + contained inside the 'div' element which has id="main" + + New in version 0.4: Support for CSS2 and CSS3 attribute selectors: + See http://www.w3.org/TR/css3-selectors/#attribute-selectors + + Version 0.4 - Simon Willison, March 25th 2003 + -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows + -- Opera 7 fails + + Version 0.5 - Carl Sverre, Jan 7th 2013 + -- Now uses jQuery-esque `hasClass` for testing class name + equality. This fixes a bug related to '-' characters being + considered not part of a 'word' in regex. + */ + + function getAllChildren(e) { + // Returns all children of element. Workaround required for IE5/Windows. Ugh. + return e.all ? e.all : e.getElementsByTagName('*'); + } + + var bad_whitespace = /[\t\r\n]/g; + + function hasClass(elem, selector) { + var className = ' ' + selector + ' '; + return ((' ' + elem.className + ' ').replace(bad_whitespace, ' ').indexOf(className) >= 0); + } + + function getElementsBySelector(selector) { + // Attempt to fail gracefully in lesser browsers + if (!document$1.getElementsByTagName) { + return []; + } + // Split selector in to tokens + var tokens = selector.split(' '); + var token, bits, tagName, found, foundCount, i, j, k, elements, currentContextIndex; + var currentContext = [document$1]; + for (i = 0; i < tokens.length; i++) { + token = tokens[i].replace(/^\s+/, '').replace(/\s+$/, ''); + if (token.indexOf('#') > -1) { + // Token is an ID selector + bits = token.split('#'); + tagName = bits[0]; + var id = bits[1]; + var element = document$1.getElementById(id); + if (!element || (tagName && element.nodeName.toLowerCase() != tagName)) { + // element not found or tag with that ID not found, return false + return []; + } + // Set currentContext to contain just this element + currentContext = [element]; + continue; // Skip to next token + } + if (token.indexOf('.') > -1) { + // Token contains a class selector + bits = token.split('.'); + tagName = bits[0]; + var className = bits[1]; + if (!tagName) { + tagName = '*'; + } + // Get elements matching tag, filter them for class selector + found = []; + foundCount = 0; + for (j = 0; j < currentContext.length; j++) { + if (tagName == '*') { + elements = getAllChildren(currentContext[j]); + } else { + elements = currentContext[j].getElementsByTagName(tagName); + } + for (k = 0; k < elements.length; k++) { + found[foundCount++] = elements[k]; + } + } + currentContext = []; + currentContextIndex = 0; + for (j = 0; j < found.length; j++) { + if (found[j].className && + _.isString(found[j].className) && // some SVG elements have classNames which are not strings + hasClass(found[j], className) + ) { + currentContext[currentContextIndex++] = found[j]; + } + } + continue; // Skip to next token + } + // Code to deal with attribute selectors + var token_match = token.match(TOKEN_MATCH_REGEX); + if (token_match) { + tagName = token_match[1]; + var attrName = token_match[2]; + var attrOperator = token_match[3]; + var attrValue = token_match[4]; + if (!tagName) { + tagName = '*'; + } + // Grab all of the tagName elements within current context + found = []; + foundCount = 0; + for (j = 0; j < currentContext.length; j++) { + if (tagName == '*') { + elements = getAllChildren(currentContext[j]); + } else { + elements = currentContext[j].getElementsByTagName(tagName); + } + for (k = 0; k < elements.length; k++) { + found[foundCount++] = elements[k]; + } + } + currentContext = []; + currentContextIndex = 0; + var checkFunction; // This function will be used to filter the elements + switch (attrOperator) { + case '=': // Equality + checkFunction = function(e) { + return (e.getAttribute(attrName) == attrValue); + }; + break; + case '~': // Match one of space seperated words + checkFunction = function(e) { + return (e.getAttribute(attrName).match(new RegExp('\\b' + attrValue + '\\b'))); + }; + break; + case '|': // Match start with value followed by optional hyphen + checkFunction = function(e) { + return (e.getAttribute(attrName).match(new RegExp('^' + attrValue + '-?'))); + }; + break; + case '^': // Match starts with value + checkFunction = function(e) { + return (e.getAttribute(attrName).indexOf(attrValue) === 0); + }; + break; + case '$': // Match ends with value - fails with "Warning" in Opera 7 + checkFunction = function(e) { + return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); + }; + break; + case '*': // Match ends with value + checkFunction = function(e) { + return (e.getAttribute(attrName).indexOf(attrValue) > -1); + }; + break; + default: + // Just test for existence of attribute + checkFunction = function(e) { + return e.getAttribute(attrName); + }; + } + currentContext = []; + currentContextIndex = 0; + for (j = 0; j < found.length; j++) { + if (checkFunction(found[j])) { + currentContext[currentContextIndex++] = found[j]; + } + } + // alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue); + continue; // Skip to next token + } + // If we get here, token is JUST an element (not a class or ID selector) + tagName = token; + found = []; + foundCount = 0; + for (j = 0; j < currentContext.length; j++) { + elements = currentContext[j].getElementsByTagName(tagName); + for (k = 0; k < elements.length; k++) { + found[foundCount++] = elements[k]; + } + } + currentContext = found; + } + return currentContext; + } + + return function(query) { + if (_.isElement(query)) { + return [query]; + } else if (_.isObject(query) && !_.isUndefined(query.length)) { + return query; + } else { + return getElementsBySelector.call(this, query); + } + }; +})(); + +var CAMPAIGN_KEYWORDS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term']; +var CLICK_IDS = ['dclid', 'fbclid', 'gclid', 'ko_click_id', 'li_fat_id', 'msclkid', 'ttclid', 'twclid', 'wbraid']; + +_.info = { + campaignParams: function(default_value) { + var kw = '', + params = {}; + _.each(CAMPAIGN_KEYWORDS, function(kwkey) { + kw = _.getQueryParam(document$1.URL, kwkey); + if (kw.length) { + params[kwkey] = kw; + } else if (default_value !== undefined) { + params[kwkey] = default_value; + } + }); + + return params; + }, + + clickParams: function() { + var id = '', + params = {}; + _.each(CLICK_IDS, function(idkey) { + id = _.getQueryParam(document$1.URL, idkey); + if (id.length) { + params[idkey] = id; + } + }); + + return params; + }, + + marketingParams: function() { + return _.extend(_.info.campaignParams(), _.info.clickParams()); + }, + + searchEngine: function(referrer) { + if (referrer.search('https?://(.*)google.([^/?]*)') === 0) { + return 'google'; + } else if (referrer.search('https?://(.*)bing.com') === 0) { + return 'bing'; + } else if (referrer.search('https?://(.*)yahoo.com') === 0) { + return 'yahoo'; + } else if (referrer.search('https?://(.*)duckduckgo.com') === 0) { + return 'duckduckgo'; + } else { + return null; + } + }, + + searchInfo: function(referrer) { + var search = _.info.searchEngine(referrer), + param = (search != 'yahoo') ? 'q' : 'p', + ret = {}; + + if (search !== null) { + ret['$search_engine'] = search; + + var keyword = _.getQueryParam(referrer, param); + if (keyword.length) { + ret['mp_keyword'] = keyword; + } + } + + return ret; + }, + + /** + * This function detects which browser is running this script. + * The order of the checks are important since many user agents + * include key words used in later checks. + */ + browser: function(user_agent, vendor, opera) { + vendor = vendor || ''; // vendor is undefined for at least IE9 + if (opera || _.includes(user_agent, ' OPR/')) { + if (_.includes(user_agent, 'Mini')) { + return 'Opera Mini'; + } + return 'Opera'; + } else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) { + return 'BlackBerry'; + } else if (_.includes(user_agent, 'IEMobile') || _.includes(user_agent, 'WPDesktop')) { + return 'Internet Explorer Mobile'; + } else if (_.includes(user_agent, 'SamsungBrowser/')) { + // https://developer.samsung.com/internet/user-agent-string-format + return 'Samsung Internet'; + } else if (_.includes(user_agent, 'Edge') || _.includes(user_agent, 'Edg/')) { + return 'Microsoft Edge'; + } else if (_.includes(user_agent, 'FBIOS')) { + return 'Facebook Mobile'; + } else if (_.includes(user_agent, 'Chrome')) { + return 'Chrome'; + } else if (_.includes(user_agent, 'CriOS')) { + return 'Chrome iOS'; + } else if (_.includes(user_agent, 'UCWEB') || _.includes(user_agent, 'UCBrowser')) { + return 'UC Browser'; + } else if (_.includes(user_agent, 'FxiOS')) { + return 'Firefox iOS'; + } else if (_.includes(vendor, 'Apple')) { + if (_.includes(user_agent, 'Mobile')) { + return 'Mobile Safari'; + } + return 'Safari'; + } else if (_.includes(user_agent, 'Android')) { + return 'Android Mobile'; + } else if (_.includes(user_agent, 'Konqueror')) { + return 'Konqueror'; + } else if (_.includes(user_agent, 'Firefox')) { + return 'Firefox'; + } else if (_.includes(user_agent, 'MSIE') || _.includes(user_agent, 'Trident/')) { + return 'Internet Explorer'; + } else if (_.includes(user_agent, 'Gecko')) { + return 'Mozilla'; + } else { + return ''; + } + }, + + /** + * This function detects which browser version is running this script, + * parsing major and minor version (e.g., 42.1). User agent strings from: + * http://www.useragentstring.com/pages/useragentstring.php + */ + browserVersion: function(userAgent, vendor, opera) { + var browser = _.info.browser(userAgent, vendor, opera); + var versionRegexs = { + 'Internet Explorer Mobile': /rv:(\d+(\.\d+)?)/, + 'Microsoft Edge': /Edge?\/(\d+(\.\d+)?)/, + 'Chrome': /Chrome\/(\d+(\.\d+)?)/, + 'Chrome iOS': /CriOS\/(\d+(\.\d+)?)/, + 'UC Browser' : /(UCBrowser|UCWEB)\/(\d+(\.\d+)?)/, + 'Safari': /Version\/(\d+(\.\d+)?)/, + 'Mobile Safari': /Version\/(\d+(\.\d+)?)/, + 'Opera': /(Opera|OPR)\/(\d+(\.\d+)?)/, + 'Firefox': /Firefox\/(\d+(\.\d+)?)/, + 'Firefox iOS': /FxiOS\/(\d+(\.\d+)?)/, + 'Konqueror': /Konqueror:(\d+(\.\d+)?)/, + 'BlackBerry': /BlackBerry (\d+(\.\d+)?)/, + 'Android Mobile': /android\s(\d+(\.\d+)?)/, + 'Samsung Internet': /SamsungBrowser\/(\d+(\.\d+)?)/, + 'Internet Explorer': /(rv:|MSIE )(\d+(\.\d+)?)/, + 'Mozilla': /rv:(\d+(\.\d+)?)/ + }; + var regex = versionRegexs[browser]; + if (regex === undefined) { + return null; + } + var matches = userAgent.match(regex); + if (!matches) { + return null; + } + return parseFloat(matches[matches.length - 2]); + }, + + os: function() { + var a = userAgent; + if (/Windows/i.test(a)) { + if (/Phone/.test(a) || /WPDesktop/.test(a)) { + return 'Windows Phone'; + } + return 'Windows'; + } else if (/(iPhone|iPad|iPod)/.test(a)) { + return 'iOS'; + } else if (/Android/.test(a)) { + return 'Android'; + } else if (/(BlackBerry|PlayBook|BB10)/i.test(a)) { + return 'BlackBerry'; + } else if (/Mac/i.test(a)) { + return 'Mac OS X'; + } else if (/Linux/.test(a)) { + return 'Linux'; + } else if (/CrOS/.test(a)) { + return 'Chrome OS'; + } else { + return ''; + } + }, + + device: function(user_agent) { + if (/Windows Phone/i.test(user_agent) || /WPDesktop/.test(user_agent)) { + return 'Windows Phone'; + } else if (/iPad/.test(user_agent)) { + return 'iPad'; + } else if (/iPod/.test(user_agent)) { + return 'iPod Touch'; + } else if (/iPhone/.test(user_agent)) { + return 'iPhone'; + } else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) { + return 'BlackBerry'; + } else if (/Android/.test(user_agent)) { + return 'Android'; + } else { + return ''; + } + }, + + referringDomain: function(referrer) { + var split = referrer.split('/'); + if (split.length >= 3) { + return split[2]; + } + return ''; + }, + + properties: function() { + return _.extend(_.strip_empty_properties({ + '$os': _.info.os(), + '$browser': _.info.browser(userAgent, navigator.vendor, windowOpera), + '$referrer': document$1.referrer, + '$referring_domain': _.info.referringDomain(document$1.referrer), + '$device': _.info.device(userAgent) + }), { + '$current_url': window$1.location.href, + '$browser_version': _.info.browserVersion(userAgent, navigator.vendor, windowOpera), + '$screen_height': screen.height, + '$screen_width': screen.width, + 'mp_lib': 'web', + '$lib_version': Config.LIB_VERSION, + '$insert_id': cheap_guid(), + 'time': _.timestamp() / 1000 // epoch time in seconds + }); + }, + + people_properties: function() { + return _.extend(_.strip_empty_properties({ + '$os': _.info.os(), + '$browser': _.info.browser(userAgent, navigator.vendor, windowOpera) + }), { + '$browser_version': _.info.browserVersion(userAgent, navigator.vendor, windowOpera) + }); + }, + + mpPageViewProperties: function() { + return _.strip_empty_properties({ + 'current_page_title': document$1.title, + 'current_domain': window$1.location.hostname, + 'current_url_path': window$1.location.pathname, + 'current_url_protocol': window$1.location.protocol, + 'current_url_search': window$1.location.search + }); + } +}; + +var cheap_guid = function(maxlen) { + var guid = Math.random().toString(36).substring(2, 10) + Math.random().toString(36).substring(2, 10); + return maxlen ? guid.substring(0, maxlen) : guid; +}; + +// naive way to extract domain name (example.com) from full hostname (my.sub.example.com) +var SIMPLE_DOMAIN_MATCH_REGEX = /[a-z0-9][a-z0-9-]*\.[a-z]+$/i; +// this next one attempts to account for some ccSLDs, e.g. extracting oxford.ac.uk from www.oxford.ac.uk +var DOMAIN_MATCH_REGEX = /[a-z0-9][a-z0-9-]+\.[a-z.]{2,6}$/i; +/** + * Attempts to extract main domain name from full hostname, using a few blunt heuristics. For + * common TLDs like .com/.org that always have a simple SLD.TLD structure (example.com), we + * simply extract the last two .-separated parts of the hostname (SIMPLE_DOMAIN_MATCH_REGEX). + * For others, we attempt to account for short ccSLD+TLD combos (.ac.uk) with the legacy + * DOMAIN_MATCH_REGEX (kept to maintain backwards compatibility with existing Mixpanel + * integrations). The only _reliable_ way to extract domain from hostname is with an up-to-date + * list like at https://publicsuffix.org/ so for cases that this helper fails at, the SDK + * offers the 'cookie_domain' config option to set it explicitly. + * @example + * extract_domain('my.sub.example.com') + * // 'example.com' + */ +var extract_domain = function(hostname) { + var domain_regex = DOMAIN_MATCH_REGEX; + var parts = hostname.split('.'); + var tld = parts[parts.length - 1]; + if (tld.length > 4 || tld === 'com' || tld === 'org') { + domain_regex = SIMPLE_DOMAIN_MATCH_REGEX; + } + var matches = hostname.match(domain_regex); + return matches ? matches[0] : ''; +}; + +var JSONStringify = null; +var JSONParse = null; +if (typeof JSON !== 'undefined') { + JSONStringify = JSON.stringify; + JSONParse = JSON.parse; +} +JSONStringify = JSONStringify || _.JSONEncode; +JSONParse = JSONParse || _.JSONDecode; + +// EXPORTS (for closure compiler) +_['toArray'] = _.toArray; +_['isObject'] = _.isObject; +_['JSONEncode'] = _.JSONEncode; +_['JSONDecode'] = _.JSONDecode; +_['isBlockedUA'] = _.isBlockedUA; +_['isEmptyObject'] = _.isEmptyObject; +_['info'] = _.info; +_['info']['device'] = _.info.device; +_['info']['browser'] = _.info.browser; +_['info']['browserVersion'] = _.info.browserVersion; +_['info']['properties'] = _.info.properties; + +/** + * DomTracker Object + * @constructor + */ +var DomTracker = function() {}; + + +// interface +DomTracker.prototype.create_properties = function() {}; +DomTracker.prototype.event_handler = function() {}; +DomTracker.prototype.after_track_handler = function() {}; + +DomTracker.prototype.init = function(mixpanel_instance) { + this.mp = mixpanel_instance; + return this; +}; + +/** + * @param {Object|string} query + * @param {string} event_name + * @param {Object=} properties + * @param {function=} user_callback + */ +DomTracker.prototype.track = function(query, event_name, properties, user_callback) { + var that = this; + var elements = _.dom_query(query); + + if (elements.length === 0) { + console$1.error('The DOM query (' + query + ') returned 0 elements'); + return; + } + + _.each(elements, function(element) { + _.register_event(element, this.override_event, function(e) { + var options = {}; + var props = that.create_properties(properties, this); + var timeout = that.mp.get_config('track_links_timeout'); + + that.event_handler(e, this, options); + + // in case the mixpanel servers don't get back to us in time + window.setTimeout(that.track_callback(user_callback, props, options, true), timeout); + + // fire the tracking event + that.mp.track(event_name, props, that.track_callback(user_callback, props, options)); + }); + }, this); + + return true; +}; + +/** + * @param {function} user_callback + * @param {Object} props + * @param {boolean=} timeout_occured + */ +DomTracker.prototype.track_callback = function(user_callback, props, options, timeout_occured) { + timeout_occured = timeout_occured || false; + var that = this; + + return function() { + // options is referenced from both callbacks, so we can have + // a 'lock' of sorts to ensure only one fires + if (options.callback_fired) { return; } + options.callback_fired = true; + + if (user_callback && user_callback(timeout_occured, props) === false) { + // user can prevent the default functionality by + // returning false from their callback + return; + } + + that.after_track_handler(props, options, timeout_occured); + }; +}; + +DomTracker.prototype.create_properties = function(properties, element) { + var props; + + if (typeof(properties) === 'function') { + props = properties(element); + } else { + props = _.extend({}, properties); + } + + return props; +}; + +/** + * LinkTracker Object + * @constructor + * @extends DomTracker + */ +var LinkTracker = function() { + this.override_event = 'click'; +}; +_.inherit(LinkTracker, DomTracker); + +LinkTracker.prototype.create_properties = function(properties, element) { + var props = LinkTracker.superclass.create_properties.apply(this, arguments); + + if (element.href) { props['url'] = element.href; } + + return props; +}; + +LinkTracker.prototype.event_handler = function(evt, element, options) { + options.new_tab = ( + evt.which === 2 || + evt.metaKey || + evt.ctrlKey || + element.target === '_blank' + ); + options.href = element.href; + + if (!options.new_tab) { + evt.preventDefault(); + } +}; + +LinkTracker.prototype.after_track_handler = function(props, options) { + if (options.new_tab) { return; } + + setTimeout(function() { + window.location = options.href; + }, 0); +}; + +/** + * FormTracker Object + * @constructor + * @extends DomTracker + */ +var FormTracker = function() { + this.override_event = 'submit'; +}; +_.inherit(FormTracker, DomTracker); + +FormTracker.prototype.event_handler = function(evt, element, options) { + options.element = element; + evt.preventDefault(); +}; + +FormTracker.prototype.after_track_handler = function(props, options) { + setTimeout(function() { + options.element.submit(); + }, 0); +}; + +// eslint-disable-line camelcase + +var logger$2 = console_with_prefix('lock'); + +/** + * SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser + * window/tab at a time will be able to access shared resources. + * + * Based on the Alur and Taubenfeld fast lock + * (http://www.cs.rochester.edu/research/synchronization/pseudocode/fastlock.html) + * with an added timeout to ensure there will be eventual progress in the event + * that a window is closed in the middle of the callback. + * + * Implementation based on the original version by David Wolever (https://github.com/wolever) + * at https://gist.github.com/wolever/5fd7573d1ef6166e8f8c4af286a69432. + * + * @example + * const myLock = new SharedLock('some-key'); + * myLock.withLock(function() { + * console.log('I hold the mutex!'); + * }); + * + * @constructor + */ +var SharedLock = function(key, options) { + options = options || {}; + + this.storageKey = key; + this.storage = options.storage || window.localStorage; + this.pollIntervalMS = options.pollIntervalMS || 100; + this.timeoutMS = options.timeoutMS || 2000; +}; + +// pass in a specific pid to test contention scenarios; otherwise +// it is chosen randomly for each acquisition attempt +SharedLock.prototype.withLock = function(lockedCB, errorCB, pid) { + if (!pid && typeof errorCB !== 'function') { + pid = errorCB; + errorCB = null; + } + + var i = pid || (new Date().getTime() + '|' + Math.random()); + var startTime = new Date().getTime(); + + var key = this.storageKey; + var pollIntervalMS = this.pollIntervalMS; + var timeoutMS = this.timeoutMS; + var storage = this.storage; + + var keyX = key + ':X'; + var keyY = key + ':Y'; + var keyZ = key + ':Z'; + + var reportError = function(err) { + errorCB && errorCB(err); + }; + + var delay = function(cb) { + if (new Date().getTime() - startTime > timeoutMS) { + logger$2.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']'); + storage.removeItem(keyZ); + storage.removeItem(keyY); + loop(); + return; + } + setTimeout(function() { + try { + cb(); + } catch(err) { + reportError(err); + } + }, pollIntervalMS * (Math.random() + 0.1)); + }; + + var waitFor = function(predicate, cb) { + if (predicate()) { + cb(); + } else { + delay(function() { + waitFor(predicate, cb); + }); + } + }; + + var getSetY = function() { + var valY = storage.getItem(keyY); + if (valY && valY !== i) { // if Y == i then this process already has the lock (useful for test cases) + return false; + } else { + storage.setItem(keyY, i); + if (storage.getItem(keyY) === i) { + return true; + } else { + if (!localStorageSupported(storage, true)) { + throw new Error('localStorage support dropped while acquiring lock'); + } + return false; + } + } + }; + + var loop = function() { + storage.setItem(keyX, i); + + waitFor(getSetY, function() { + if (storage.getItem(keyX) === i) { + criticalSection(); + return; + } + + delay(function() { + if (storage.getItem(keyY) !== i) { + loop(); + return; + } + waitFor(function() { + return !storage.getItem(keyZ); + }, criticalSection); + }); + }); + }; + + var criticalSection = function() { + storage.setItem(keyZ, '1'); + try { + lockedCB(); + } finally { + storage.removeItem(keyZ); + if (storage.getItem(keyY) === i) { + storage.removeItem(keyY); + } + if (storage.getItem(keyX) === i) { + storage.removeItem(keyX); + } + } + }; + + try { + if (localStorageSupported(storage, true)) { + loop(); + } else { + throw new Error('localStorage support check failed'); + } + } catch(err) { + reportError(err); + } +}; + +// eslint-disable-line camelcase + +var logger$1 = console_with_prefix('batch'); + +/** + * RequestQueue: queue for batching API requests with localStorage backup for retries. + * Maintains an in-memory queue which represents the source of truth for the current + * page, but also writes all items out to a copy in the browser's localStorage, which + * can be read on subsequent pageloads and retried. For batchability, all the request + * items in the queue should be of the same type (events, people updates, group updates) + * so they can be sent in a single request to the same API endpoint. + * + * LocalStorage keying and locking: In order for reloads and subsequent pageloads of + * the same site to access the same persisted data, they must share the same localStorage + * key (for instance based on project token and queue type). Therefore access to the + * localStorage entry is guarded by an asynchronous mutex (SharedLock) to prevent + * simultaneously open windows/tabs from overwriting each other's data (which would lead + * to data loss in some situations). + * @constructor + */ +var RequestQueue = function(storageKey, options) { + options = options || {}; + this.storageKey = storageKey; + this.storage = options.storage || window.localStorage; + this.reportError = options.errorReporter || _.bind(logger$1.error, logger$1); + this.lock = new SharedLock(storageKey, {storage: this.storage}); + + this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios + + this.memQueue = []; +}; + +/** + * Add one item to queues (memory and localStorage). The queued entry includes + * the given item along with an auto-generated ID and a "flush-after" timestamp. + * It is expected that the item will be sent over the network and dequeued + * before the flush-after time; if this doesn't happen it is considered orphaned + * (e.g., the original tab where it was enqueued got closed before it could be + * sent) and the item can be sent by any tab that finds it in localStorage. + * + * The final callback param is called with a param indicating success or + * failure of the enqueue operation; it is asynchronous because the localStorage + * lock is asynchronous. + */ +RequestQueue.prototype.enqueue = function(item, flushInterval, cb) { + var queueEntry = { + 'id': cheap_guid(), + 'flushAfter': new Date().getTime() + flushInterval * 2, + 'payload': item + }; + + this.lock.withLock(_.bind(function lockAcquired() { + var succeeded; + try { + var storedQueue = this.readFromStorage(); + storedQueue.push(queueEntry); + succeeded = this.saveToStorage(storedQueue); + if (succeeded) { + // only add to in-memory queue when storage succeeds + this.memQueue.push(queueEntry); + } + } catch(err) { + this.reportError('Error enqueueing item', item); + succeeded = false; + } + if (cb) { + cb(succeeded); + } + }, this), _.bind(function lockFailure(err) { + this.reportError('Error acquiring storage lock', err); + if (cb) { + cb(false); + } + }, this), this.pid); +}; + +/** + * Read out the given number of queue entries. If this.memQueue + * has fewer than batchSize items, then look for "orphaned" items + * in the persisted queue (items where the 'flushAfter' time has + * already passed). + */ +RequestQueue.prototype.fillBatch = function(batchSize) { + var batch = this.memQueue.slice(0, batchSize); + if (batch.length < batchSize) { + // don't need lock just to read events; localStorage is thread-safe + // and the worst that could happen is a duplicate send of some + // orphaned events, which will be deduplicated on the server side + var storedQueue = this.readFromStorage(); + if (storedQueue.length) { + // item IDs already in batch; don't duplicate out of storage + var idsInBatch = {}; // poor man's Set + _.each(batch, function(item) { idsInBatch[item['id']] = true; }); + + for (var i = 0; i < storedQueue.length; i++) { + var item = storedQueue[i]; + if (new Date().getTime() > item['flushAfter'] && !idsInBatch[item['id']]) { + item.orphaned = true; + batch.push(item); + if (batch.length >= batchSize) { + break; + } + } + } + } + } + return batch; +}; + +/** + * Remove items with matching 'id' from array (immutably) + * also remove any item without a valid id (e.g., malformed + * storage entries). + */ +var filterOutIDsAndInvalid = function(items, idSet) { + var filteredItems = []; + _.each(items, function(item) { + if (item['id'] && !idSet[item['id']]) { + filteredItems.push(item); + } + }); + return filteredItems; +}; + +/** + * Remove items with matching IDs from both in-memory queue + * and persisted queue + */ +RequestQueue.prototype.removeItemsByID = function(ids, cb) { + var idSet = {}; // poor man's Set + _.each(ids, function(id) { idSet[id] = true; }); + + this.memQueue = filterOutIDsAndInvalid(this.memQueue, idSet); + + var removeFromStorage = _.bind(function() { + var succeeded; + try { + var storedQueue = this.readFromStorage(); + storedQueue = filterOutIDsAndInvalid(storedQueue, idSet); + succeeded = this.saveToStorage(storedQueue); + + // an extra check: did storage report success but somehow + // the items are still there? + if (succeeded) { + storedQueue = this.readFromStorage(); + for (var i = 0; i < storedQueue.length; i++) { + var item = storedQueue[i]; + if (item['id'] && !!idSet[item['id']]) { + this.reportError('Item not removed from storage'); + return false; + } + } + } + } catch(err) { + this.reportError('Error removing items', ids); + succeeded = false; + } + return succeeded; + }, this); + + this.lock.withLock(function lockAcquired() { + var succeeded = removeFromStorage(); + if (cb) { + cb(succeeded); + } + }, _.bind(function lockFailure(err) { + var succeeded = false; + this.reportError('Error acquiring storage lock', err); + if (!localStorageSupported(this.storage, true)) { + // Looks like localStorage writes have stopped working sometime after + // initialization (probably full), and so nobody can acquire locks + // anymore. Consider it temporarily safe to remove items without the + // lock, since nobody's writing successfully anyway. + succeeded = removeFromStorage(); + if (!succeeded) { + // OK, we couldn't even write out the smaller queue. Try clearing it + // entirely. + try { + this.storage.removeItem(this.storageKey); + } catch(err) { + this.reportError('Error clearing queue', err); + } + } + } + if (cb) { + cb(succeeded); + } + }, this), this.pid); +}; + +// internal helper for RequestQueue.updatePayloads +var updatePayloads = function(existingItems, itemsToUpdate) { + var newItems = []; + _.each(existingItems, function(item) { + var id = item['id']; + if (id in itemsToUpdate) { + var newPayload = itemsToUpdate[id]; + if (newPayload !== null) { + item['payload'] = newPayload; + newItems.push(item); + } + } else { + // no update + newItems.push(item); + } + }); + return newItems; +}; + +/** + * Update payloads of given items in both in-memory queue and + * persisted queue. Items set to null are removed from queues. + */ +RequestQueue.prototype.updatePayloads = function(itemsToUpdate, cb) { + this.memQueue = updatePayloads(this.memQueue, itemsToUpdate); + this.lock.withLock(_.bind(function lockAcquired() { + var succeeded; + try { + var storedQueue = this.readFromStorage(); + storedQueue = updatePayloads(storedQueue, itemsToUpdate); + succeeded = this.saveToStorage(storedQueue); + } catch(err) { + this.reportError('Error updating items', itemsToUpdate); + succeeded = false; + } + if (cb) { + cb(succeeded); + } + }, this), _.bind(function lockFailure(err) { + this.reportError('Error acquiring storage lock', err); + if (cb) { + cb(false); + } + }, this), this.pid); +}; + +/** + * Read and parse items array from localStorage entry, handling + * malformed/missing data if necessary. + */ +RequestQueue.prototype.readFromStorage = function() { + var storageEntry; + try { + storageEntry = this.storage.getItem(this.storageKey); + if (storageEntry) { + storageEntry = JSONParse(storageEntry); + if (!_.isArray(storageEntry)) { + this.reportError('Invalid storage entry:', storageEntry); + storageEntry = null; + } + } + } catch (err) { + this.reportError('Error retrieving queue', err); + storageEntry = null; + } + return storageEntry || []; +}; + +/** + * Serialize the given items array to localStorage. + */ +RequestQueue.prototype.saveToStorage = function(queue) { + try { + this.storage.setItem(this.storageKey, JSONStringify(queue)); + return true; + } catch (err) { + this.reportError('Error saving queue', err); + return false; + } +}; + +/** + * Clear out queues (memory and localStorage). + */ +RequestQueue.prototype.clear = function() { + this.memQueue = []; + this.storage.removeItem(this.storageKey); +}; + +// eslint-disable-line camelcase + +// maximum interval between request retries after exponential backoff +var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes + +var logger = console_with_prefix('batch'); + +/** + * RequestBatcher: manages the queueing, flushing, retry etc of requests of one + * type (events, people, groups). + * Uses RequestQueue to manage the backing store. + * @constructor + */ +var RequestBatcher = function(storageKey, options) { + this.errorReporter = options.errorReporter; + this.queue = new RequestQueue(storageKey, { + errorReporter: _.bind(this.reportError, this), + storage: options.storage + }); + + this.libConfig = options.libConfig; + this.sendRequest = options.sendRequestFunc; + this.beforeSendHook = options.beforeSendHook; + this.stopAllBatching = options.stopAllBatchingFunc; + + // seed variable batch size + flush interval with configured values + this.batchSize = this.libConfig['batch_size']; + this.flushInterval = this.libConfig['batch_flush_interval_ms']; + + this.stopped = !this.libConfig['batch_autostart']; + this.consecutiveRemovalFailures = 0; + + // extra client-side dedupe + this.itemIdsSentSuccessfully = {}; +}; + +/** + * Add one item to queue. + */ +RequestBatcher.prototype.enqueue = function(item, cb) { + this.queue.enqueue(item, this.flushInterval, cb); +}; + +/** + * Start flushing batches at the configured time interval. Must call + * this method upon SDK init in order to send anything over the network. + */ +RequestBatcher.prototype.start = function() { + this.stopped = false; + this.consecutiveRemovalFailures = 0; + this.flush(); +}; + +/** + * Stop flushing batches. Can be restarted by calling start(). + */ +RequestBatcher.prototype.stop = function() { + this.stopped = true; + if (this.timeoutID) { + clearTimeout(this.timeoutID); + this.timeoutID = null; + } +}; + +/** + * Clear out queue. + */ +RequestBatcher.prototype.clear = function() { + this.queue.clear(); +}; + +/** + * Restore batch size configuration to whatever is set in the main SDK. + */ +RequestBatcher.prototype.resetBatchSize = function() { + this.batchSize = this.libConfig['batch_size']; +}; + +/** + * Restore flush interval time configuration to whatever is set in the main SDK. + */ +RequestBatcher.prototype.resetFlush = function() { + this.scheduleFlush(this.libConfig['batch_flush_interval_ms']); +}; + +/** + * Schedule the next flush in the given number of milliseconds. + */ +RequestBatcher.prototype.scheduleFlush = function(flushMS) { + this.flushInterval = flushMS; + if (!this.stopped) { // don't schedule anymore if batching has been stopped + this.timeoutID = setTimeout(_.bind(this.flush, this), this.flushInterval); + } +}; + +/** + * Flush one batch to network. Depending on success/failure modes, it will either + * remove the batch from the queue or leave it in for retry, and schedule the next + * flush. In cases of most network or API failures, it will back off exponentially + * when retrying. + * @param {Object} [options] + * @param {boolean} [options.sendBeacon] - whether to send batch with + * navigator.sendBeacon (only useful for sending batches before page unloads, as + * sendBeacon offers no callbacks or status indications) + */ +RequestBatcher.prototype.flush = function(options) { + try { + + if (this.requestInProgress) { + logger.log('Flush: Request already in progress'); + return; + } + + options = options || {}; + var timeoutMS = this.libConfig['batch_request_timeout_ms']; + var startTime = new Date().getTime(); + var currentBatchSize = this.batchSize; + var batch = this.queue.fillBatch(currentBatchSize); + var dataForRequest = []; + var transformedItems = {}; + _.each(batch, function(item) { + var payload = item['payload']; + if (this.beforeSendHook && !item.orphaned) { + payload = this.beforeSendHook(payload); + } + if (payload) { + // mp_sent_by_lib_version prop captures which lib version actually + // sends each event (regardless of which version originally queued + // it for sending) + if (payload['event'] && payload['properties']) { + payload['properties'] = _.extend( + {}, + payload['properties'], + {'mp_sent_by_lib_version': Config.LIB_VERSION} + ); + } + var addPayload = true; + var itemId = item['id']; + if (itemId) { + if ((this.itemIdsSentSuccessfully[itemId] || 0) > 5) { + this.reportError('[dupe] item ID sent too many times, not sending', { + item: item, + batchSize: batch.length, + timesSent: this.itemIdsSentSuccessfully[itemId] + }); + addPayload = false; + } + } else { + this.reportError('[dupe] found item with no ID', {item: item}); + } + + if (addPayload) { + dataForRequest.push(payload); + } + } + transformedItems[item['id']] = payload; + }, this); + if (dataForRequest.length < 1) { + this.resetFlush(); + return; // nothing to do + } + + this.requestInProgress = true; + + var batchSendCallback = _.bind(function(res) { + this.requestInProgress = false; + + try { + + // handle API response in a try-catch to make sure we can reset the + // flush operation if something goes wrong + + var removeItemsFromQueue = false; + if (options.unloading) { + // update persisted data to include hook transformations + this.queue.updatePayloads(transformedItems); + } else if ( + _.isObject(res) && + res.error === 'timeout' && + new Date().getTime() - startTime >= timeoutMS + ) { + this.reportError('Network timeout; retrying'); + this.flush(); + } else if ( + _.isObject(res) && + res.xhr_req && + (res.xhr_req['status'] >= 500 || res.xhr_req['status'] === 429 || res.error === 'timeout') + ) { + // network or API error, or 429 Too Many Requests, retry + var retryMS = this.flushInterval * 2; + var headers = res.xhr_req['responseHeaders']; + if (headers) { + var retryAfter = headers['Retry-After']; + if (retryAfter) { + retryMS = (parseInt(retryAfter, 10) * 1000) || retryMS; + } + } + retryMS = Math.min(MAX_RETRY_INTERVAL_MS, retryMS); + this.reportError('Error; retry in ' + retryMS + ' ms'); + this.scheduleFlush(retryMS); + } else if (_.isObject(res) && res.xhr_req && res.xhr_req['status'] === 413) { + // 413 Payload Too Large + if (batch.length > 1) { + var halvedBatchSize = Math.max(1, Math.floor(currentBatchSize / 2)); + this.batchSize = Math.min(this.batchSize, halvedBatchSize, batch.length - 1); + this.reportError('413 response; reducing batch size to ' + this.batchSize); + this.resetFlush(); + } else { + this.reportError('Single-event request too large; dropping', batch); + this.resetBatchSize(); + removeItemsFromQueue = true; + } + } else { + // successful network request+response; remove each item in batch from queue + // (even if it was e.g. a 400, in which case retrying won't help) + removeItemsFromQueue = true; + } + + if (removeItemsFromQueue) { + this.queue.removeItemsByID( + _.map(batch, function(item) { return item['id']; }), + _.bind(function(succeeded) { + if (succeeded) { + this.consecutiveRemovalFailures = 0; + this.flush(); // handle next batch if the queue isn't empty + } else { + this.reportError('Failed to remove items from queue'); + if (++this.consecutiveRemovalFailures > 5) { + this.reportError('Too many queue failures; disabling batching system.'); + this.stopAllBatching(); + } else { + this.resetFlush(); + } + } + }, this) + ); + + // client-side dedupe + _.each(batch, _.bind(function(item) { + var itemId = item['id']; + if (itemId) { + this.itemIdsSentSuccessfully[itemId] = this.itemIdsSentSuccessfully[itemId] || 0; + this.itemIdsSentSuccessfully[itemId]++; + if (this.itemIdsSentSuccessfully[itemId] > 5) { + this.reportError('[dupe] item ID sent too many times', { + item: item, + batchSize: batch.length, + timesSent: this.itemIdsSentSuccessfully[itemId] + }); + } + } else { + this.reportError('[dupe] found item with no ID while removing', {item: item}); + } + }, this)); + } + + } catch(err) { + this.reportError('Error handling API response', err); + this.resetFlush(); + } + }, this); + var requestOptions = { + method: 'POST', + verbose: true, + ignore_json_errors: true, // eslint-disable-line camelcase + timeout_ms: timeoutMS // eslint-disable-line camelcase + }; + if (options.unloading) { + requestOptions.transport = 'sendBeacon'; + } + logger.log('MIXPANEL REQUEST:', dataForRequest); + this.sendRequest(dataForRequest, requestOptions, batchSendCallback); + + } catch(err) { + this.reportError('Error flushing request queue', err); + this.resetFlush(); + } +}; + +/** + * Log error to global logger and optional user-defined logger. + */ +RequestBatcher.prototype.reportError = function(msg, err) { + logger.error.apply(logger.error, arguments); + if (this.errorReporter) { + try { + if (!(err instanceof Error)) { + err = new Error(msg); + } + this.errorReporter(msg, err); + } catch(err) { + logger.error(err); + } + } +}; + +/** + * A function used to track a Mixpanel event (e.g. MixpanelLib.track) + * @callback trackFunction + * @param {String} event_name The name of the event. This can be anything the user does - 'Button Click', 'Sign Up', 'Item Purchased', etc. + * @param {Object} [properties] A set of properties to include with the event you're sending. These describe the user who did the event or details about the event itself. + * @param {Function} [callback] If provided, the callback function will be called after tracking the event. + */ + +/** Public **/ + +var GDPR_DEFAULT_PERSISTENCE_PREFIX = '__mp_opt_in_out_'; + +/** + * Opt the user in to data tracking and cookies/localstorage for the given token + * @param {string} token - Mixpanel project tracking token + * @param {Object} [options] + * @param {trackFunction} [options.track] - function used for tracking a Mixpanel event to record the opt-in action + * @param {string} [options.trackEventName] - event name to be used for tracking the opt-in action + * @param {Object} [options.trackProperties] - set of properties to be tracked along with the opt-in action + * @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage + * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookieExpiration] - number of days until the opt-in cookie expires + * @param {string} [options.cookieDomain] - custom cookie domain + * @param {boolean} [options.crossSiteCookie] - whether the opt-in cookie is set as cross-site-enabled + * @param {boolean} [options.crossSubdomainCookie] - whether the opt-in cookie is set as cross-subdomain or not + * @param {boolean} [options.secureCookie] - whether the opt-in cookie is set as secure or not + */ +function optIn(token, options) { + _optInOut(true, token, options); +} + +/** + * Opt the user out of data tracking and cookies/localstorage for the given token + * @param {string} token - Mixpanel project tracking token + * @param {Object} [options] + * @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage + * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookieExpiration] - number of days until the opt-out cookie expires + * @param {string} [options.cookieDomain] - custom cookie domain + * @param {boolean} [options.crossSiteCookie] - whether the opt-in cookie is set as cross-site-enabled + * @param {boolean} [options.crossSubdomainCookie] - whether the opt-out cookie is set as cross-subdomain or not + * @param {boolean} [options.secureCookie] - whether the opt-out cookie is set as secure or not + */ +function optOut(token, options) { + _optInOut(false, token, options); +} + +/** + * Check whether the user has opted in to data tracking and cookies/localstorage for the given token + * @param {string} token - Mixpanel project tracking token + * @param {Object} [options] + * @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage + * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name + * @returns {boolean} whether the user has opted in to the given opt type + */ +function hasOptedIn(token, options) { + return _getStorageValue(token, options) === '1'; +} + +/** + * Check whether the user has opted out of data tracking and cookies/localstorage for the given token + * @param {string} token - Mixpanel project tracking token + * @param {Object} [options] + * @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage + * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name + * @param {boolean} [options.ignoreDnt] - flag to ignore browser DNT settings and always return false + * @returns {boolean} whether the user has opted out of the given opt type + */ +function hasOptedOut(token, options) { + if (_hasDoNotTrackFlagOn(options)) { + console$1.warn('This browser has "Do Not Track" enabled. This will prevent the Mixpanel SDK from sending any data. To ignore the "Do Not Track" browser setting, initialize the Mixpanel instance with the config "ignore_dnt: true"'); + return true; + } + var optedOut = _getStorageValue(token, options) === '0'; + if (optedOut) { + console$1.warn('You are opted out of Mixpanel tracking. This will prevent the Mixpanel SDK from sending any data.'); + } + return optedOut; +} + +/** + * Wrap a MixpanelLib method with a check for whether the user is opted out of data tracking and cookies/localstorage for the given token + * If the user has opted out, return early instead of executing the method. + * If a callback argument was provided, execute it passing the 0 error code. + * @param {function} method - wrapped method to be executed if the user has not opted out + * @returns {*} the result of executing method OR undefined if the user has opted out + */ +function addOptOutCheckMixpanelLib(method) { + return _addOptOutCheck(method, function(name) { + return this.get_config(name); + }); +} + +/** + * Wrap a MixpanelPeople method with a check for whether the user is opted out of data tracking and cookies/localstorage for the given token + * If the user has opted out, return early instead of executing the method. + * If a callback argument was provided, execute it passing the 0 error code. + * @param {function} method - wrapped method to be executed if the user has not opted out + * @returns {*} the result of executing method OR undefined if the user has opted out + */ +function addOptOutCheckMixpanelPeople(method) { + return _addOptOutCheck(method, function(name) { + return this._get_config(name); + }); +} + +/** + * Wrap a MixpanelGroup method with a check for whether the user is opted out of data tracking and cookies/localstorage for the given token + * If the user has opted out, return early instead of executing the method. + * If a callback argument was provided, execute it passing the 0 error code. + * @param {function} method - wrapped method to be executed if the user has not opted out + * @returns {*} the result of executing method OR undefined if the user has opted out + */ +function addOptOutCheckMixpanelGroup(method) { + return _addOptOutCheck(method, function(name) { + return this._get_config(name); + }); +} + +/** + * Clear the user's opt in/out status of data tracking and cookies/localstorage for the given token + * @param {string} token - Mixpanel project tracking token + * @param {Object} [options] + * @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage + * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookieExpiration] - number of days until the opt-in cookie expires + * @param {string} [options.cookieDomain] - custom cookie domain + * @param {boolean} [options.crossSiteCookie] - whether the opt-in cookie is set as cross-site-enabled + * @param {boolean} [options.crossSubdomainCookie] - whether the opt-in cookie is set as cross-subdomain or not + * @param {boolean} [options.secureCookie] - whether the opt-in cookie is set as secure or not + */ +function clearOptInOut(token, options) { + options = options || {}; + _getStorage(options).remove( + _getStorageKey(token, options), !!options.crossSubdomainCookie, options.cookieDomain + ); +} + +/** Private **/ + +/** + * Get storage util + * @param {Object} [options] + * @param {string} [options.persistenceType] + * @returns {object} either _.cookie or _.localstorage + */ +function _getStorage(options) { + options = options || {}; + return options.persistenceType === 'localStorage' ? _.localStorage : _.cookie; +} + +/** + * Get the name of the cookie that is used for the given opt type (tracking, cookie, etc.) + * @param {string} token - Mixpanel project tracking token + * @param {Object} [options] + * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name + * @returns {string} the name of the cookie for the given opt type + */ +function _getStorageKey(token, options) { + options = options || {}; + return (options.persistencePrefix || GDPR_DEFAULT_PERSISTENCE_PREFIX) + token; +} + +/** + * Get the value of the cookie that is used for the given opt type (tracking, cookie, etc.) + * @param {string} token - Mixpanel project tracking token + * @param {Object} [options] + * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name + * @returns {string} the value of the cookie for the given opt type + */ +function _getStorageValue(token, options) { + return _getStorage(options).get(_getStorageKey(token, options)); +} + +/** + * Check whether the user has set the DNT/doNotTrack setting to true in their browser + * @param {Object} [options] + * @param {string} [options.window] - alternate window object to check; used to force various DNT settings in browser tests + * @param {boolean} [options.ignoreDnt] - flag to ignore browser DNT settings and always return false + * @returns {boolean} whether the DNT setting is true + */ +function _hasDoNotTrackFlagOn(options) { + if (options && options.ignoreDnt) { + return false; + } + var win = (options && options.window) || window$1; + var nav = win['navigator'] || {}; + var hasDntOn = false; + + _.each([ + nav['doNotTrack'], // standard + nav['msDoNotTrack'], + win['doNotTrack'] + ], function(dntValue) { + if (_.includes([true, 1, '1', 'yes'], dntValue)) { + hasDntOn = true; + } + }); + + return hasDntOn; +} + +/** + * Set cookie/localstorage for the user indicating that they are opted in or out for the given opt type + * @param {boolean} optValue - whether to opt the user in or out for the given opt type + * @param {string} token - Mixpanel project tracking token + * @param {Object} [options] + * @param {trackFunction} [options.track] - function used for tracking a Mixpanel event to record the opt-in action + * @param {string} [options.trackEventName] - event name to be used for tracking the opt-in action + * @param {Object} [options.trackProperties] - set of properties to be tracked along with the opt-in action + * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookieExpiration] - number of days until the opt-in cookie expires + * @param {string} [options.cookieDomain] - custom cookie domain + * @param {boolean} [options.crossSiteCookie] - whether the opt-in cookie is set as cross-site-enabled + * @param {boolean} [options.crossSubdomainCookie] - whether the opt-in cookie is set as cross-subdomain or not + * @param {boolean} [options.secureCookie] - whether the opt-in cookie is set as secure or not + */ +function _optInOut(optValue, token, options) { + if (!_.isString(token) || !token.length) { + console$1.error('gdpr.' + (optValue ? 'optIn' : 'optOut') + ' called with an invalid token'); + return; + } + + options = options || {}; + + _getStorage(options).set( + _getStorageKey(token, options), + optValue ? 1 : 0, + _.isNumber(options.cookieExpiration) ? options.cookieExpiration : null, + !!options.crossSubdomainCookie, + !!options.secureCookie, + !!options.crossSiteCookie, + options.cookieDomain + ); + + if (options.track && optValue) { // only track event if opting in (optValue=true) + options.track(options.trackEventName || '$opt_in', options.trackProperties, { + 'send_immediately': true + }); + } +} + +/** + * Wrap a method with a check for whether the user is opted out of data tracking and cookies/localstorage for the given token + * If the user has opted out, return early instead of executing the method. + * If a callback argument was provided, execute it passing the 0 error code. + * @param {function} method - wrapped method to be executed if the user has not opted out + * @param {function} getConfigValue - getter function for the Mixpanel API token and other options to be used with opt-out check + * @returns {*} the result of executing method OR undefined if the user has opted out + */ +function _addOptOutCheck(method, getConfigValue) { + return function() { + var optedOut = false; + + try { + var token = getConfigValue.call(this, 'token'); + var ignoreDnt = getConfigValue.call(this, 'ignore_dnt'); + var persistenceType = getConfigValue.call(this, 'opt_out_tracking_persistence_type'); + var persistencePrefix = getConfigValue.call(this, 'opt_out_tracking_cookie_prefix'); + var win = getConfigValue.call(this, 'window'); // used to override window during browser tests + + if (token) { // if there was an issue getting the token, continue method execution as normal + optedOut = hasOptedOut(token, { + ignoreDnt: ignoreDnt, + persistenceType: persistenceType, + persistencePrefix: persistencePrefix, + window: win + }); + } + } catch(err) { + console$1.error('Unexpected error when checking tracking opt-out status: ' + err); + } + + if (!optedOut) { + return method.apply(this, arguments); + } + + var callback = arguments[arguments.length - 1]; + if (typeof(callback) === 'function') { + callback(0); + } + + return; + }; +} + +/** @const */ var SET_ACTION = '$set'; +/** @const */ var SET_ONCE_ACTION = '$set_once'; +/** @const */ var UNSET_ACTION = '$unset'; +/** @const */ var ADD_ACTION = '$add'; +/** @const */ var APPEND_ACTION = '$append'; +/** @const */ var UNION_ACTION = '$union'; +/** @const */ var REMOVE_ACTION = '$remove'; +/** @const */ var DELETE_ACTION = '$delete'; + +// Common internal methods for mixpanel.people and mixpanel.group APIs. +// These methods shouldn't involve network I/O. +var apiActions = { + set_action: function(prop, to) { + var data = {}; + var $set = {}; + if (_.isObject(prop)) { + _.each(prop, function(v, k) { + if (!this._is_reserved_property(k)) { + $set[k] = v; + } + }, this); + } else { + $set[prop] = to; + } + + data[SET_ACTION] = $set; + return data; + }, + + unset_action: function(prop) { + var data = {}; + var $unset = []; + if (!_.isArray(prop)) { + prop = [prop]; + } + + _.each(prop, function(k) { + if (!this._is_reserved_property(k)) { + $unset.push(k); + } + }, this); + + data[UNSET_ACTION] = $unset; + return data; + }, + + set_once_action: function(prop, to) { + var data = {}; + var $set_once = {}; + if (_.isObject(prop)) { + _.each(prop, function(v, k) { + if (!this._is_reserved_property(k)) { + $set_once[k] = v; + } + }, this); + } else { + $set_once[prop] = to; + } + data[SET_ONCE_ACTION] = $set_once; + return data; + }, + + union_action: function(list_name, values) { + var data = {}; + var $union = {}; + if (_.isObject(list_name)) { + _.each(list_name, function(v, k) { + if (!this._is_reserved_property(k)) { + $union[k] = _.isArray(v) ? v : [v]; + } + }, this); + } else { + $union[list_name] = _.isArray(values) ? values : [values]; + } + data[UNION_ACTION] = $union; + return data; + }, + + append_action: function(list_name, value) { + var data = {}; + var $append = {}; + if (_.isObject(list_name)) { + _.each(list_name, function(v, k) { + if (!this._is_reserved_property(k)) { + $append[k] = v; + } + }, this); + } else { + $append[list_name] = value; + } + data[APPEND_ACTION] = $append; + return data; + }, + + remove_action: function(list_name, value) { + var data = {}; + var $remove = {}; + if (_.isObject(list_name)) { + _.each(list_name, function(v, k) { + if (!this._is_reserved_property(k)) { + $remove[k] = v; + } + }, this); + } else { + $remove[list_name] = value; + } + data[REMOVE_ACTION] = $remove; + return data; + }, + + delete_action: function() { + var data = {}; + data[DELETE_ACTION] = ''; + return data; + } +}; + +/** + * Mixpanel Group Object + * @constructor + */ +var MixpanelGroup = function() {}; + +_.extend(MixpanelGroup.prototype, apiActions); + +MixpanelGroup.prototype._init = function(mixpanel_instance, group_key, group_id) { + this._mixpanel = mixpanel_instance; + this._group_key = group_key; + this._group_id = group_id; +}; + +/** + * Set properties on a group. + * + * ### Usage: + * + * mixpanel.get_group('company', 'mixpanel').set('Location', '405 Howard'); + * + * // or set multiple properties at once + * mixpanel.get_group('company', 'mixpanel').set({ + * 'Location': '405 Howard', + * 'Founded' : 2009, + * }); + * // properties can be strings, integers, dates, or lists + * + * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values. + * @param {*} [to] A value to set on the given property name + * @param {Function} [callback] If provided, the callback will be called after the tracking event + */ +MixpanelGroup.prototype.set = addOptOutCheckMixpanelGroup(function(prop, to, callback) { + var data = this.set_action(prop, to); + if (_.isObject(prop)) { + callback = to; + } + return this._send_request(data, callback); +}); + +/** + * Set properties on a group, only if they do not yet exist. + * This will not overwrite previous group property values, unlike + * group.set(). + * + * ### Usage: + * + * mixpanel.get_group('company', 'mixpanel').set_once('Location', '405 Howard'); + * + * // or set multiple properties at once + * mixpanel.get_group('company', 'mixpanel').set_once({ + * 'Location': '405 Howard', + * 'Founded' : 2009, + * }); + * // properties can be strings, integers, lists or dates + * + * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values. + * @param {*} [to] A value to set on the given property name + * @param {Function} [callback] If provided, the callback will be called after the tracking event + */ +MixpanelGroup.prototype.set_once = addOptOutCheckMixpanelGroup(function(prop, to, callback) { + var data = this.set_once_action(prop, to); + if (_.isObject(prop)) { + callback = to; + } + return this._send_request(data, callback); +}); + +/** + * Unset properties on a group permanently. + * + * ### Usage: + * + * mixpanel.get_group('company', 'mixpanel').unset('Founded'); + * + * @param {String} prop The name of the property. + * @param {Function} [callback] If provided, the callback will be called after the tracking event + */ +MixpanelGroup.prototype.unset = addOptOutCheckMixpanelGroup(function(prop, callback) { + var data = this.unset_action(prop); + return this._send_request(data, callback); +}); + +/** + * Merge a given list with a list-valued group property, excluding duplicate values. + * + * ### Usage: + * + * // merge a value to a list, creating it if needed + * mixpanel.get_group('company', 'mixpanel').union('Location', ['San Francisco', 'London']); + * + * @param {String} list_name Name of the property. + * @param {Array} values Values to merge with the given property + * @param {Function} [callback] If provided, the callback will be called after the tracking event + */ +MixpanelGroup.prototype.union = addOptOutCheckMixpanelGroup(function(list_name, values, callback) { + if (_.isObject(list_name)) { + callback = values; + } + var data = this.union_action(list_name, values); + return this._send_request(data, callback); +}); + +/** + * Permanently delete a group. + * + * ### Usage: + * + * mixpanel.get_group('company', 'mixpanel').delete(); + * + * @param {Function} [callback] If provided, the callback will be called after the tracking event + */ +MixpanelGroup.prototype['delete'] = addOptOutCheckMixpanelGroup(function(callback) { + // bracket notation above prevents a minification error related to reserved words + var data = this.delete_action(); + return this._send_request(data, callback); +}); + +/** + * Remove a property from a group. The value will be ignored if doesn't exist. + * + * ### Usage: + * + * mixpanel.get_group('company', 'mixpanel').remove('Location', 'London'); + * + * @param {String} list_name Name of the property. + * @param {Object} value Value to remove from the given group property + * @param {Function} [callback] If provided, the callback will be called after the tracking event + */ +MixpanelGroup.prototype.remove = addOptOutCheckMixpanelGroup(function(list_name, value, callback) { + var data = this.remove_action(list_name, value); + return this._send_request(data, callback); +}); + +MixpanelGroup.prototype._send_request = function(data, callback) { + data['$group_key'] = this._group_key; + data['$group_id'] = this._group_id; + data['$token'] = this._get_config('token'); + + var date_encoded_data = _.encodeDates(data); + return this._mixpanel._track_or_batch({ + type: 'groups', + data: date_encoded_data, + endpoint: this._get_config('api_host') + '/groups/', + batcher: this._mixpanel.request_batchers.groups + }, callback); +}; + +MixpanelGroup.prototype._is_reserved_property = function(prop) { + return prop === '$group_key' || prop === '$group_id'; +}; + +MixpanelGroup.prototype._get_config = function(conf) { + return this._mixpanel.get_config(conf); +}; + +MixpanelGroup.prototype.toString = function() { + return this._mixpanel.toString() + '.group.' + this._group_key + '.' + this._group_id; +}; + +// MixpanelGroup Exports +MixpanelGroup.prototype['remove'] = MixpanelGroup.prototype.remove; +MixpanelGroup.prototype['set'] = MixpanelGroup.prototype.set; +MixpanelGroup.prototype['set_once'] = MixpanelGroup.prototype.set_once; +MixpanelGroup.prototype['union'] = MixpanelGroup.prototype.union; +MixpanelGroup.prototype['unset'] = MixpanelGroup.prototype.unset; +MixpanelGroup.prototype['toString'] = MixpanelGroup.prototype.toString; + +/** + * Mixpanel People Object + * @constructor + */ +var MixpanelPeople = function() {}; + +_.extend(MixpanelPeople.prototype, apiActions); + +MixpanelPeople.prototype._init = function(mixpanel_instance) { + this._mixpanel = mixpanel_instance; +}; + +/* +* Set properties on a user record. +* +* ### Usage: +* +* mixpanel.people.set('gender', 'm'); +* +* // or set multiple properties at once +* mixpanel.people.set({ +* 'Company': 'Acme', +* 'Plan': 'Premium', +* 'Upgrade date': new Date() +* }); +* // properties can be strings, integers, dates, or lists +* +* @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values. +* @param {*} [to] A value to set on the given property name +* @param {Function} [callback] If provided, the callback will be called after tracking the event. +*/ +MixpanelPeople.prototype.set = addOptOutCheckMixpanelPeople(function(prop, to, callback) { + var data = this.set_action(prop, to); + if (_.isObject(prop)) { + callback = to; + } + // make sure that the referrer info has been updated and saved + if (this._get_config('save_referrer')) { + this._mixpanel['persistence'].update_referrer_info(document.referrer); + } + + // update $set object with default people properties + data[SET_ACTION] = _.extend( + {}, + _.info.people_properties(), + this._mixpanel['persistence'].get_referrer_info(), + data[SET_ACTION] + ); + return this._send_request(data, callback); +}); + +/* +* Set properties on a user record, only if they do not yet exist. +* This will not overwrite previous people property values, unlike +* people.set(). +* +* ### Usage: +* +* mixpanel.people.set_once('First Login Date', new Date()); +* +* // or set multiple properties at once +* mixpanel.people.set_once({ +* 'First Login Date': new Date(), +* 'Starting Plan': 'Premium' +* }); +* +* // properties can be strings, integers or dates +* +* @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values. +* @param {*} [to] A value to set on the given property name +* @param {Function} [callback] If provided, the callback will be called after tracking the event. +*/ +MixpanelPeople.prototype.set_once = addOptOutCheckMixpanelPeople(function(prop, to, callback) { + var data = this.set_once_action(prop, to); + if (_.isObject(prop)) { + callback = to; + } + return this._send_request(data, callback); +}); + +/* +* Unset properties on a user record (permanently removes the properties and their values from a profile). +* +* ### Usage: +* +* mixpanel.people.unset('gender'); +* +* // or unset multiple properties at once +* mixpanel.people.unset(['gender', 'Company']); +* +* @param {Array|String} prop If a string, this is the name of the property. If an array, this is a list of property names. +* @param {Function} [callback] If provided, the callback will be called after tracking the event. +*/ +MixpanelPeople.prototype.unset = addOptOutCheckMixpanelPeople(function(prop, callback) { + var data = this.unset_action(prop); + return this._send_request(data, callback); +}); + +/* +* Increment/decrement numeric people analytics properties. +* +* ### Usage: +* +* mixpanel.people.increment('page_views', 1); +* +* // or, for convenience, if you're just incrementing a counter by +* // 1, you can simply do +* mixpanel.people.increment('page_views'); +* +* // to decrement a counter, pass a negative number +* mixpanel.people.increment('credits_left', -1); +* +* // like mixpanel.people.set(), you can increment multiple +* // properties at once: +* mixpanel.people.increment({ +* counter1: 1, +* counter2: 6 +* }); +* +* @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and numeric values. +* @param {Number} [by] An amount to increment the given property +* @param {Function} [callback] If provided, the callback will be called after tracking the event. +*/ +MixpanelPeople.prototype.increment = addOptOutCheckMixpanelPeople(function(prop, by, callback) { + var data = {}; + var $add = {}; + if (_.isObject(prop)) { + _.each(prop, function(v, k) { + if (!this._is_reserved_property(k)) { + if (isNaN(parseFloat(v))) { + console$1.error('Invalid increment value passed to mixpanel.people.increment - must be a number'); + return; + } else { + $add[k] = v; + } + } + }, this); + callback = by; + } else { + // convenience: mixpanel.people.increment('property'); will + // increment 'property' by 1 + if (_.isUndefined(by)) { + by = 1; + } + $add[prop] = by; + } + data[ADD_ACTION] = $add; + + return this._send_request(data, callback); +}); + +/* +* Append a value to a list-valued people analytics property. +* +* ### Usage: +* +* // append a value to a list, creating it if needed +* mixpanel.people.append('pages_visited', 'homepage'); +* +* // like mixpanel.people.set(), you can append multiple +* // properties at once: +* mixpanel.people.append({ +* list1: 'bob', +* list2: 123 +* }); +* +* @param {Object|String} list_name If a string, this is the name of the property. If an object, this is an associative array of names and values. +* @param {*} [value] value An item to append to the list +* @param {Function} [callback] If provided, the callback will be called after tracking the event. +*/ +MixpanelPeople.prototype.append = addOptOutCheckMixpanelPeople(function(list_name, value, callback) { + if (_.isObject(list_name)) { + callback = value; + } + var data = this.append_action(list_name, value); + return this._send_request(data, callback); +}); + +/* +* Remove a value from a list-valued people analytics property. +* +* ### Usage: +* +* mixpanel.people.remove('School', 'UCB'); +* +* @param {Object|String} list_name If a string, this is the name of the property. If an object, this is an associative array of names and values. +* @param {*} [value] value Item to remove from the list +* @param {Function} [callback] If provided, the callback will be called after tracking the event. +*/ +MixpanelPeople.prototype.remove = addOptOutCheckMixpanelPeople(function(list_name, value, callback) { + if (_.isObject(list_name)) { + callback = value; + } + var data = this.remove_action(list_name, value); + return this._send_request(data, callback); +}); + +/* +* Merge a given list with a list-valued people analytics property, +* excluding duplicate values. +* +* ### Usage: +* +* // merge a value to a list, creating it if needed +* mixpanel.people.union('pages_visited', 'homepage'); +* +* // like mixpanel.people.set(), you can append multiple +* // properties at once: +* mixpanel.people.union({ +* list1: 'bob', +* list2: 123 +* }); +* +* // like mixpanel.people.append(), you can append multiple +* // values to the same list: +* mixpanel.people.union({ +* list1: ['bob', 'billy'] +* }); +* +* @param {Object|String} list_name If a string, this is the name of the property. If an object, this is an associative array of names and values. +* @param {*} [value] Value / values to merge with the given property +* @param {Function} [callback] If provided, the callback will be called after tracking the event. +*/ +MixpanelPeople.prototype.union = addOptOutCheckMixpanelPeople(function(list_name, values, callback) { + if (_.isObject(list_name)) { + callback = values; + } + var data = this.union_action(list_name, values); + return this._send_request(data, callback); +}); + +/* + * Record that you have charged the current user a certain amount + * of money. Charges recorded with track_charge() will appear in the + * Mixpanel revenue report. + * + * ### Usage: + * + * // charge a user $50 + * mixpanel.people.track_charge(50); + * + * // charge a user $30.50 on the 2nd of january + * mixpanel.people.track_charge(30.50, { + * '$time': new Date('jan 1 2012') + * }); + * + * @param {Number} amount The amount of money charged to the current user + * @param {Object} [properties] An associative array of properties associated with the charge + * @param {Function} [callback] If provided, the callback will be called when the server responds + * @deprecated + */ +MixpanelPeople.prototype.track_charge = addOptOutCheckMixpanelPeople(function(amount, properties, callback) { + if (!_.isNumber(amount)) { + amount = parseFloat(amount); + if (isNaN(amount)) { + console$1.error('Invalid value passed to mixpanel.people.track_charge - must be a number'); + return; + } + } + + return this.append('$transactions', _.extend({ + '$amount': amount + }, properties), callback); +}); + +/* + * Permanently clear all revenue report transactions from the + * current user's people analytics profile. + * + * ### Usage: + * + * mixpanel.people.clear_charges(); + * + * @param {Function} [callback] If provided, the callback will be called after tracking the event. + * @deprecated + */ +MixpanelPeople.prototype.clear_charges = function(callback) { + return this.set('$transactions', [], callback); +}; + +/* +* Permanently deletes the current people analytics profile from +* Mixpanel (using the current distinct_id). +* +* ### Usage: +* +* // remove the all data you have stored about the current user +* mixpanel.people.delete_user(); +* +*/ +MixpanelPeople.prototype.delete_user = function() { + if (!this._identify_called()) { + console$1.error('mixpanel.people.delete_user() requires you to call identify() first'); + return; + } + var data = {'$delete': this._mixpanel.get_distinct_id()}; + return this._send_request(data); +}; + +MixpanelPeople.prototype.toString = function() { + return this._mixpanel.toString() + '.people'; +}; + +MixpanelPeople.prototype._send_request = function(data, callback) { + data['$token'] = this._get_config('token'); + data['$distinct_id'] = this._mixpanel.get_distinct_id(); + var device_id = this._mixpanel.get_property('$device_id'); + var user_id = this._mixpanel.get_property('$user_id'); + var had_persisted_distinct_id = this._mixpanel.get_property('$had_persisted_distinct_id'); + if (device_id) { + data['$device_id'] = device_id; + } + if (user_id) { + data['$user_id'] = user_id; + } + if (had_persisted_distinct_id) { + data['$had_persisted_distinct_id'] = had_persisted_distinct_id; + } + + var date_encoded_data = _.encodeDates(data); + + if (!this._identify_called()) { + this._enqueue(data); + if (!_.isUndefined(callback)) { + if (this._get_config('verbose')) { + callback({status: -1, error: null}); + } else { + callback(-1); + } + } + return _.truncate(date_encoded_data, 255); + } + + return this._mixpanel._track_or_batch({ + type: 'people', + data: date_encoded_data, + endpoint: this._get_config('api_host') + '/engage/', + batcher: this._mixpanel.request_batchers.people + }, callback); +}; + +MixpanelPeople.prototype._get_config = function(conf_var) { + return this._mixpanel.get_config(conf_var); +}; + +MixpanelPeople.prototype._identify_called = function() { + return this._mixpanel._flags.identify_called === true; +}; + +// Queue up engage operations if identify hasn't been called yet. +MixpanelPeople.prototype._enqueue = function(data) { + if (SET_ACTION in data) { + this._mixpanel['persistence']._add_to_people_queue(SET_ACTION, data); + } else if (SET_ONCE_ACTION in data) { + this._mixpanel['persistence']._add_to_people_queue(SET_ONCE_ACTION, data); + } else if (UNSET_ACTION in data) { + this._mixpanel['persistence']._add_to_people_queue(UNSET_ACTION, data); + } else if (ADD_ACTION in data) { + this._mixpanel['persistence']._add_to_people_queue(ADD_ACTION, data); + } else if (APPEND_ACTION in data) { + this._mixpanel['persistence']._add_to_people_queue(APPEND_ACTION, data); + } else if (REMOVE_ACTION in data) { + this._mixpanel['persistence']._add_to_people_queue(REMOVE_ACTION, data); + } else if (UNION_ACTION in data) { + this._mixpanel['persistence']._add_to_people_queue(UNION_ACTION, data); + } else { + console$1.error('Invalid call to _enqueue():', data); + } +}; + +MixpanelPeople.prototype._flush_one_queue = function(action, action_method, callback, queue_to_params_fn) { + var _this = this; + var queued_data = _.extend({}, this._mixpanel['persistence']._get_queue(action)); + var action_params = queued_data; + + if (!_.isUndefined(queued_data) && _.isObject(queued_data) && !_.isEmptyObject(queued_data)) { + _this._mixpanel['persistence']._pop_from_people_queue(action, queued_data); + if (queue_to_params_fn) { + action_params = queue_to_params_fn(queued_data); + } + action_method.call(_this, action_params, function(response, data) { + // on bad response, we want to add it back to the queue + if (response === 0) { + _this._mixpanel['persistence']._add_to_people_queue(action, queued_data); + } + if (!_.isUndefined(callback)) { + callback(response, data); + } + }); + } +}; + +// Flush queued engage operations - order does not matter, +// and there are network level race conditions anyway +MixpanelPeople.prototype._flush = function( + _set_callback, _add_callback, _append_callback, _set_once_callback, _union_callback, _unset_callback, _remove_callback +) { + var _this = this; + var $append_queue = this._mixpanel['persistence']._get_queue(APPEND_ACTION); + var $remove_queue = this._mixpanel['persistence']._get_queue(REMOVE_ACTION); + + this._flush_one_queue(SET_ACTION, this.set, _set_callback); + this._flush_one_queue(SET_ONCE_ACTION, this.set_once, _set_once_callback); + this._flush_one_queue(UNSET_ACTION, this.unset, _unset_callback, function(queue) { return _.keys(queue); }); + this._flush_one_queue(ADD_ACTION, this.increment, _add_callback); + this._flush_one_queue(UNION_ACTION, this.union, _union_callback); + + // we have to fire off each $append individually since there is + // no concat method server side + if (!_.isUndefined($append_queue) && _.isArray($append_queue) && $append_queue.length) { + var $append_item; + var append_callback = function(response, data) { + if (response === 0) { + _this._mixpanel['persistence']._add_to_people_queue(APPEND_ACTION, $append_item); + } + if (!_.isUndefined(_append_callback)) { + _append_callback(response, data); + } + }; + for (var i = $append_queue.length - 1; i >= 0; i--) { + $append_item = $append_queue.pop(); + if (!_.isEmptyObject($append_item)) { + _this.append($append_item, append_callback); + } + } + // Save the shortened append queue + _this._mixpanel['persistence'].save(); + } + + // same for $remove + if (!_.isUndefined($remove_queue) && _.isArray($remove_queue) && $remove_queue.length) { + var $remove_item; + var remove_callback = function(response, data) { + if (response === 0) { + _this._mixpanel['persistence']._add_to_people_queue(REMOVE_ACTION, $remove_item); + } + if (!_.isUndefined(_remove_callback)) { + _remove_callback(response, data); + } + }; + for (var j = $remove_queue.length - 1; j >= 0; j--) { + $remove_item = $remove_queue.pop(); + if (!_.isEmptyObject($remove_item)) { + _this.remove($remove_item, remove_callback); + } + } + _this._mixpanel['persistence'].save(); + } +}; + +MixpanelPeople.prototype._is_reserved_property = function(prop) { + return prop === '$distinct_id' || prop === '$token' || prop === '$device_id' || prop === '$user_id' || prop === '$had_persisted_distinct_id'; +}; + +// MixpanelPeople Exports +MixpanelPeople.prototype['set'] = MixpanelPeople.prototype.set; +MixpanelPeople.prototype['set_once'] = MixpanelPeople.prototype.set_once; +MixpanelPeople.prototype['unset'] = MixpanelPeople.prototype.unset; +MixpanelPeople.prototype['increment'] = MixpanelPeople.prototype.increment; +MixpanelPeople.prototype['append'] = MixpanelPeople.prototype.append; +MixpanelPeople.prototype['remove'] = MixpanelPeople.prototype.remove; +MixpanelPeople.prototype['union'] = MixpanelPeople.prototype.union; +MixpanelPeople.prototype['track_charge'] = MixpanelPeople.prototype.track_charge; +MixpanelPeople.prototype['clear_charges'] = MixpanelPeople.prototype.clear_charges; +MixpanelPeople.prototype['delete_user'] = MixpanelPeople.prototype.delete_user; +MixpanelPeople.prototype['toString'] = MixpanelPeople.prototype.toString; + +/* + * Constants + */ +/** @const */ var SET_QUEUE_KEY = '__mps'; +/** @const */ var SET_ONCE_QUEUE_KEY = '__mpso'; +/** @const */ var UNSET_QUEUE_KEY = '__mpus'; +/** @const */ var ADD_QUEUE_KEY = '__mpa'; +/** @const */ var APPEND_QUEUE_KEY = '__mpap'; +/** @const */ var REMOVE_QUEUE_KEY = '__mpr'; +/** @const */ var UNION_QUEUE_KEY = '__mpu'; +// This key is deprecated, but we want to check for it to see whether aliasing is allowed. +/** @const */ var PEOPLE_DISTINCT_ID_KEY = '$people_distinct_id'; +/** @const */ var ALIAS_ID_KEY = '__alias'; +/** @const */ var EVENT_TIMERS_KEY = '__timers'; +/** @const */ var RESERVED_PROPERTIES = [ + SET_QUEUE_KEY, + SET_ONCE_QUEUE_KEY, + UNSET_QUEUE_KEY, + ADD_QUEUE_KEY, + APPEND_QUEUE_KEY, + REMOVE_QUEUE_KEY, + UNION_QUEUE_KEY, + PEOPLE_DISTINCT_ID_KEY, + ALIAS_ID_KEY, + EVENT_TIMERS_KEY +]; + +/** + * Mixpanel Persistence Object + * @constructor + */ +var MixpanelPersistence = function(config) { + this['props'] = {}; + this.campaign_params_saved = false; + + if (config['persistence_name']) { + this.name = 'mp_' + config['persistence_name']; + } else { + this.name = 'mp_' + config['token'] + '_mixpanel'; + } + + var storage_type = config['persistence']; + if (storage_type !== 'cookie' && storage_type !== 'localStorage') { + console$1.critical('Unknown persistence type ' + storage_type + '; falling back to cookie'); + storage_type = config['persistence'] = 'cookie'; + } + + if (storage_type === 'localStorage' && _.localStorage.is_supported()) { + this.storage = _.localStorage; + } else { + this.storage = _.cookie; + } + + this.load(); + this.update_config(config); + this.upgrade(config); + this.save(); +}; + +MixpanelPersistence.prototype.properties = function() { + var p = {}; + // Filter out reserved properties + _.each(this['props'], function(v, k) { + if (!_.include(RESERVED_PROPERTIES, k)) { + p[k] = v; + } + }); + return p; +}; + +MixpanelPersistence.prototype.load = function() { + if (this.disabled) { return; } + + var entry = this.storage.parse(this.name); + + if (entry) { + this['props'] = _.extend({}, entry); + } +}; + +MixpanelPersistence.prototype.upgrade = function(config) { + var upgrade_from_old_lib = config['upgrade'], + old_cookie_name, + old_cookie; + + if (upgrade_from_old_lib) { + old_cookie_name = 'mp_super_properties'; + // Case where they had a custom cookie name before. + if (typeof(upgrade_from_old_lib) === 'string') { + old_cookie_name = upgrade_from_old_lib; + } + + old_cookie = this.storage.parse(old_cookie_name); + + // remove the cookie + this.storage.remove(old_cookie_name); + this.storage.remove(old_cookie_name, true); + + if (old_cookie) { + this['props'] = _.extend( + this['props'], + old_cookie['all'], + old_cookie['events'] + ); + } + } + + if (!config['cookie_name'] && config['name'] !== 'mixpanel') { + // special case to handle people with cookies of the form + // mp_TOKEN_INSTANCENAME from the first release of this library + old_cookie_name = 'mp_' + config['token'] + '_' + config['name']; + old_cookie = this.storage.parse(old_cookie_name); + + if (old_cookie) { + this.storage.remove(old_cookie_name); + this.storage.remove(old_cookie_name, true); + + // Save the prop values that were in the cookie from before - + // this should only happen once as we delete the old one. + this.register_once(old_cookie); + } + } + + if (this.storage === _.localStorage) { + old_cookie = _.cookie.parse(this.name); + + _.cookie.remove(this.name); + _.cookie.remove(this.name, true); + + if (old_cookie) { + this.register_once(old_cookie); + } + } +}; + +MixpanelPersistence.prototype.save = function() { + if (this.disabled) { return; } + this.storage.set( + this.name, + _.JSONEncode(this['props']), + this.expire_days, + this.cross_subdomain, + this.secure, + this.cross_site, + this.cookie_domain + ); +}; + +MixpanelPersistence.prototype.remove = function() { + // remove both domain and subdomain cookies + this.storage.remove(this.name, false, this.cookie_domain); + this.storage.remove(this.name, true, this.cookie_domain); +}; + +// removes the storage entry and deletes all loaded data +// forced name for tests +MixpanelPersistence.prototype.clear = function() { + this.remove(); + this['props'] = {}; +}; + +/** +* @param {Object} props +* @param {*=} default_value +* @param {number=} days +*/ +MixpanelPersistence.prototype.register_once = function(props, default_value, days) { + if (_.isObject(props)) { + if (typeof(default_value) === 'undefined') { default_value = 'None'; } + this.expire_days = (typeof(days) === 'undefined') ? this.default_expiry : days; + + _.each(props, function(val, prop) { + if (!this['props'].hasOwnProperty(prop) || this['props'][prop] === default_value) { + this['props'][prop] = val; + } + }, this); + + this.save(); + + return true; + } + return false; +}; + +/** +* @param {Object} props +* @param {number=} days +*/ +MixpanelPersistence.prototype.register = function(props, days) { + if (_.isObject(props)) { + this.expire_days = (typeof(days) === 'undefined') ? this.default_expiry : days; + + _.extend(this['props'], props); + + this.save(); + + return true; + } + return false; +}; + +MixpanelPersistence.prototype.unregister = function(prop) { + if (prop in this['props']) { + delete this['props'][prop]; + this.save(); + } +}; + +MixpanelPersistence.prototype.update_search_keyword = function(referrer) { + this.register(_.info.searchInfo(referrer)); +}; + +// EXPORTED METHOD, we test this directly. +MixpanelPersistence.prototype.update_referrer_info = function(referrer) { + // If referrer doesn't exist, we want to note the fact that it was type-in traffic. + this.register_once({ + '$initial_referrer': referrer || '$direct', + '$initial_referring_domain': _.info.referringDomain(referrer) || '$direct' + }, ''); +}; + +MixpanelPersistence.prototype.get_referrer_info = function() { + return _.strip_empty_properties({ + '$initial_referrer': this['props']['$initial_referrer'], + '$initial_referring_domain': this['props']['$initial_referring_domain'] + }); +}; + +// safely fills the passed in object with stored properties, +// does not override any properties defined in both +// returns the passed in object +MixpanelPersistence.prototype.safe_merge = function(props) { + _.each(this['props'], function(val, prop) { + if (!(prop in props)) { + props[prop] = val; + } + }); + + return props; +}; + +MixpanelPersistence.prototype.update_config = function(config) { + this.default_expiry = this.expire_days = config['cookie_expiration']; + this.set_disabled(config['disable_persistence']); + this.set_cookie_domain(config['cookie_domain']); + this.set_cross_site(config['cross_site_cookie']); + this.set_cross_subdomain(config['cross_subdomain_cookie']); + this.set_secure(config['secure_cookie']); +}; + +MixpanelPersistence.prototype.set_disabled = function(disabled) { + this.disabled = disabled; + if (this.disabled) { + this.remove(); + } else { + this.save(); + } +}; + +MixpanelPersistence.prototype.set_cookie_domain = function(cookie_domain) { + if (cookie_domain !== this.cookie_domain) { + this.remove(); + this.cookie_domain = cookie_domain; + this.save(); + } +}; + +MixpanelPersistence.prototype.set_cross_site = function(cross_site) { + if (cross_site !== this.cross_site) { + this.cross_site = cross_site; + this.remove(); + this.save(); + } +}; + +MixpanelPersistence.prototype.set_cross_subdomain = function(cross_subdomain) { + if (cross_subdomain !== this.cross_subdomain) { + this.cross_subdomain = cross_subdomain; + this.remove(); + this.save(); + } +}; + +MixpanelPersistence.prototype.get_cross_subdomain = function() { + return this.cross_subdomain; +}; + +MixpanelPersistence.prototype.set_secure = function(secure) { + if (secure !== this.secure) { + this.secure = secure ? true : false; + this.remove(); + this.save(); + } +}; + +MixpanelPersistence.prototype._add_to_people_queue = function(queue, data) { + var q_key = this._get_queue_key(queue), + q_data = data[queue], + set_q = this._get_or_create_queue(SET_ACTION), + set_once_q = this._get_or_create_queue(SET_ONCE_ACTION), + unset_q = this._get_or_create_queue(UNSET_ACTION), + add_q = this._get_or_create_queue(ADD_ACTION), + union_q = this._get_or_create_queue(UNION_ACTION), + remove_q = this._get_or_create_queue(REMOVE_ACTION, []), + append_q = this._get_or_create_queue(APPEND_ACTION, []); + + if (q_key === SET_QUEUE_KEY) { + // Update the set queue - we can override any existing values + _.extend(set_q, q_data); + // if there was a pending increment, override it + // with the set. + this._pop_from_people_queue(ADD_ACTION, q_data); + // if there was a pending union, override it + // with the set. + this._pop_from_people_queue(UNION_ACTION, q_data); + this._pop_from_people_queue(UNSET_ACTION, q_data); + } else if (q_key === SET_ONCE_QUEUE_KEY) { + // only queue the data if there is not already a set_once call for it. + _.each(q_data, function(v, k) { + if (!(k in set_once_q)) { + set_once_q[k] = v; + } + }); + this._pop_from_people_queue(UNSET_ACTION, q_data); + } else if (q_key === UNSET_QUEUE_KEY) { + _.each(q_data, function(prop) { + + // undo previously-queued actions on this key + _.each([set_q, set_once_q, add_q, union_q], function(enqueued_obj) { + if (prop in enqueued_obj) { + delete enqueued_obj[prop]; + } + }); + _.each(append_q, function(append_obj) { + if (prop in append_obj) { + delete append_obj[prop]; + } + }); + + unset_q[prop] = true; + + }); + } else if (q_key === ADD_QUEUE_KEY) { + _.each(q_data, function(v, k) { + // If it exists in the set queue, increment + // the value + if (k in set_q) { + set_q[k] += v; + } else { + // If it doesn't exist, update the add + // queue + if (!(k in add_q)) { + add_q[k] = 0; + } + add_q[k] += v; + } + }, this); + this._pop_from_people_queue(UNSET_ACTION, q_data); + } else if (q_key === UNION_QUEUE_KEY) { + _.each(q_data, function(v, k) { + if (_.isArray(v)) { + if (!(k in union_q)) { + union_q[k] = []; + } + // We may send duplicates, the server will dedup them. + union_q[k] = union_q[k].concat(v); + } + }); + this._pop_from_people_queue(UNSET_ACTION, q_data); + } else if (q_key === REMOVE_QUEUE_KEY) { + remove_q.push(q_data); + this._pop_from_people_queue(APPEND_ACTION, q_data); + } else if (q_key === APPEND_QUEUE_KEY) { + append_q.push(q_data); + this._pop_from_people_queue(UNSET_ACTION, q_data); + } + + console$1.log('MIXPANEL PEOPLE REQUEST (QUEUED, PENDING IDENTIFY):'); + console$1.log(data); + + this.save(); +}; + +MixpanelPersistence.prototype._pop_from_people_queue = function(queue, data) { + var q = this._get_queue(queue); + if (!_.isUndefined(q)) { + _.each(data, function(v, k) { + if (queue === APPEND_ACTION || queue === REMOVE_ACTION) { + // list actions: only remove if both k+v match + // e.g. remove should not override append in a case like + // append({foo: 'bar'}); remove({foo: 'qux'}) + _.each(q, function(queued_action) { + if (queued_action[k] === v) { + delete queued_action[k]; + } + }); + } else { + delete q[k]; + } + }, this); + + this.save(); + } +}; + +MixpanelPersistence.prototype._get_queue_key = function(queue) { + if (queue === SET_ACTION) { + return SET_QUEUE_KEY; + } else if (queue === SET_ONCE_ACTION) { + return SET_ONCE_QUEUE_KEY; + } else if (queue === UNSET_ACTION) { + return UNSET_QUEUE_KEY; + } else if (queue === ADD_ACTION) { + return ADD_QUEUE_KEY; + } else if (queue === APPEND_ACTION) { + return APPEND_QUEUE_KEY; + } else if (queue === REMOVE_ACTION) { + return REMOVE_QUEUE_KEY; + } else if (queue === UNION_ACTION) { + return UNION_QUEUE_KEY; + } else { + console$1.error('Invalid queue:', queue); + } +}; + +MixpanelPersistence.prototype._get_queue = function(queue) { + return this['props'][this._get_queue_key(queue)]; +}; +MixpanelPersistence.prototype._get_or_create_queue = function(queue, default_val) { + var key = this._get_queue_key(queue); + default_val = _.isUndefined(default_val) ? {} : default_val; + + return this['props'][key] || (this['props'][key] = default_val); +}; + +MixpanelPersistence.prototype.set_event_timer = function(event_name, timestamp) { + var timers = this['props'][EVENT_TIMERS_KEY] || {}; + timers[event_name] = timestamp; + this['props'][EVENT_TIMERS_KEY] = timers; + this.save(); +}; + +MixpanelPersistence.prototype.remove_event_timer = function(event_name) { + var timers = this['props'][EVENT_TIMERS_KEY] || {}; + var timestamp = timers[event_name]; + if (!_.isUndefined(timestamp)) { + delete this['props'][EVENT_TIMERS_KEY][event_name]; + this.save(); + } + return timestamp; +}; + +/* + * Mixpanel JS Library + * + * Copyright 2012, Mixpanel, Inc. All Rights Reserved + * http://mixpanel.com/ + * + * Includes portions of Underscore.js + * http://documentcloud.github.com/underscore/ + * (c) 2011 Jeremy Ashkenas, DocumentCloud Inc. + * Released under the MIT License. + */ + +// ==ClosureCompiler== +// @compilation_level ADVANCED_OPTIMIZATIONS +// @output_file_name mixpanel-2.8.min.js +// ==/ClosureCompiler== + +/* +SIMPLE STYLE GUIDE: + +this.x === public function +this._x === internal - only use within this file +this.__x === private - only use within the class + +Globals should be all caps +*/ + +var init_type; // MODULE or SNIPPET loader +var mixpanel_master; // main mixpanel instance / object +var INIT_MODULE = 0; +var INIT_SNIPPET = 1; + +var IDENTITY_FUNC = function(x) {return x;}; +var NOOP_FUNC = function() {}; + +/** @const */ var PRIMARY_INSTANCE_NAME = 'mixpanel'; +/** @const */ var PAYLOAD_TYPE_BASE64 = 'base64'; +/** @const */ var PAYLOAD_TYPE_JSON = 'json'; +/** @const */ var DEVICE_ID_PREFIX = '$device:'; + + +/* + * Dynamic... constants? Is that an oxymoron? + */ +// http://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/ +// https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#withCredentials +var USE_XHR = (window$1.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest()); + +// IE<10 does not support cross-origin XHR's but script tags +// with defer won't block window.onload; ENQUEUE_REQUESTS +// should only be true for Opera<12 +var ENQUEUE_REQUESTS = !USE_XHR && (userAgent.indexOf('MSIE') === -1) && (userAgent.indexOf('Mozilla') === -1); + +// save reference to navigator.sendBeacon so it can be minified +var sendBeacon = null; +if (navigator['sendBeacon']) { + sendBeacon = function() { + // late reference to navigator.sendBeacon to allow patching/spying + return navigator['sendBeacon'].apply(navigator, arguments); + }; +} + +/* + * Module-level globals + */ +var DEFAULT_CONFIG = { + 'api_host': 'https://api-js.mixpanel.com', + 'api_method': 'POST', + 'api_transport': 'XHR', + 'api_payload_format': PAYLOAD_TYPE_BASE64, + 'app_host': 'https://mixpanel.com', + 'cdn': 'https://cdn.mxpnl.com', + 'cross_site_cookie': false, + 'cross_subdomain_cookie': true, + 'error_reporter': NOOP_FUNC, + 'persistence': 'cookie', + 'persistence_name': '', + 'cookie_domain': '', + 'cookie_name': '', + 'loaded': NOOP_FUNC, + 'track_marketing': true, + 'track_pageview': false, + 'skip_first_touch_marketing': false, + 'store_google': true, + 'save_referrer': true, + 'test': false, + 'verbose': false, + 'img': false, + 'debug': false, + 'track_links_timeout': 300, + 'cookie_expiration': 365, + 'upgrade': false, + 'disable_persistence': false, + 'disable_cookie': false, + 'secure_cookie': false, + 'ip': true, + 'opt_out_tracking_by_default': false, + 'opt_out_persistence_by_default': false, + 'opt_out_tracking_persistence_type': 'localStorage', + 'opt_out_tracking_cookie_prefix': null, + 'property_blacklist': [], + 'xhr_headers': {}, // { header: value, header2: value } + 'ignore_dnt': false, + 'batch_requests': true, + 'batch_size': 50, + 'batch_flush_interval_ms': 5000, + 'batch_request_timeout_ms': 90000, + 'batch_autostart': true, + 'hooks': {} +}; + +var DOM_LOADED = false; + +/** + * Mixpanel Library Object + * @constructor + */ +var MixpanelLib = function() {}; + + +/** + * create_mplib(token:string, config:object, name:string) + * + * This function is used by the init method of MixpanelLib objects + * as well as the main initializer at the end of the JSLib (that + * initializes document.mixpanel as well as any additional instances + * declared before this file has loaded). + */ +var create_mplib = function(token, config, name) { + var instance, + target = (name === PRIMARY_INSTANCE_NAME) ? mixpanel_master : mixpanel_master[name]; + + if (target && init_type === INIT_MODULE) { + instance = target; + } else { + if (target && !_.isArray(target)) { + console$1.error('You have already initialized ' + name); + return; + } + instance = new MixpanelLib(); + } + + instance._cached_groups = {}; // cache groups in a pool + + instance._init(token, config, name); + + instance['people'] = new MixpanelPeople(); + instance['people']._init(instance); + + if (!instance.get_config('skip_first_touch_marketing')) { + // We need null UTM params in the object because + // UTM parameters act as a tuple. If any UTM param + // is present, then we set all UTM params including + // empty ones together + var utm_params = _.info.campaignParams(null); + var initial_utm_params = {}; + var has_utm = false; + _.each(utm_params, function(utm_value, utm_key) { + initial_utm_params['initial_' + utm_key] = utm_value; + if (utm_value) { + has_utm = true; + } + }); + if (has_utm) { + instance['people'].set_once(initial_utm_params); + } + } + + // if any instance on the page has debug = true, we set the + // global debug to be true + Config.DEBUG = Config.DEBUG || instance.get_config('debug'); + + // if target is not defined, we called init after the lib already + // loaded, so there won't be an array of things to execute + if (!_.isUndefined(target) && _.isArray(target)) { + // Crunch through the people queue first - we queue this data up & + // flush on identify, so it's better to do all these operations first + instance._execute_array.call(instance['people'], target['people']); + instance._execute_array(target); + } + + return instance; +}; + +// Initialization methods + +/** + * This function initializes a new instance of the Mixpanel tracking object. + * All new instances are added to the main mixpanel object as sub properties (such as + * mixpanel.library_name) and also returned by this function. To define a + * second instance on the page, you would call: + * + * mixpanel.init('new token', { your: 'config' }, 'library_name'); + * + * and use it like so: + * + * mixpanel.library_name.track(...); + * + * @param {String} token Your Mixpanel API token + * @param {Object} [config] A dictionary of config options to override. See a list of default config options. + * @param {String} [name] The name for the new mixpanel instance that you want created + */ +MixpanelLib.prototype.init = function (token, config, name) { + if (_.isUndefined(name)) { + this.report_error('You must name your new library: init(token, config, name)'); + return; + } + if (name === PRIMARY_INSTANCE_NAME) { + this.report_error('You must initialize the main mixpanel object right after you include the Mixpanel js snippet'); + return; + } + + var instance = create_mplib(token, config, name); + mixpanel_master[name] = instance; + instance._loaded(); + + return instance; +}; + +// mixpanel._init(token:string, config:object, name:string) +// +// This function sets up the current instance of the mixpanel +// library. The difference between this method and the init(...) +// method is this one initializes the actual instance, whereas the +// init(...) method sets up a new library and calls _init on it. +// +MixpanelLib.prototype._init = function(token, config, name) { + config = config || {}; + + this['__loaded'] = true; + this['config'] = {}; + + var variable_features = {}; + + // default to JSON payload for standard mixpanel.com API hosts + if (!('api_payload_format' in config)) { + var api_host = config['api_host'] || DEFAULT_CONFIG['api_host']; + if (api_host.match(/\.mixpanel\.com/)) { + variable_features['api_payload_format'] = PAYLOAD_TYPE_JSON; + } + } + + this.set_config(_.extend({}, DEFAULT_CONFIG, variable_features, config, { + 'name': name, + 'token': token, + 'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc' + })); + + this['_jsc'] = NOOP_FUNC; + + this.__dom_loaded_queue = []; + this.__request_queue = []; + this.__disabled_events = []; + this._flags = { + 'disable_all_events': false, + 'identify_called': false + }; + + // set up request queueing/batching + this.request_batchers = {}; + this._batch_requests = this.get_config('batch_requests'); + if (this._batch_requests) { + if (!_.localStorage.is_supported(true) || !USE_XHR) { + this._batch_requests = false; + console$1.log('Turning off Mixpanel request-queueing; needs XHR and localStorage support'); + } else { + this.init_batchers(); + if (sendBeacon && window$1.addEventListener) { + // Before page closes or hides (user tabs away etc), attempt to flush any events + // queued up via navigator.sendBeacon. Since sendBeacon doesn't report success/failure, + // events will not be removed from the persistent store; if the site is loaded again, + // the events will be flushed again on startup and deduplicated on the Mixpanel server + // side. + // There is no reliable way to capture only page close events, so we lean on the + // visibilitychange and pagehide events as recommended at + // https://developer.mozilla.org/en-US/docs/Web/API/Window/unload_event#usage_notes. + // These events fire when the user clicks away from the current page/tab, so will occur + // more frequently than page unload, but are the only mechanism currently for capturing + // this scenario somewhat reliably. + var flush_on_unload = _.bind(function() { + if (!this.request_batchers.events.stopped) { + this.request_batchers.events.flush({unloading: true}); + } + }, this); + window$1.addEventListener('pagehide', function(ev) { + if (ev['persisted']) { + flush_on_unload(); + } + }); + window$1.addEventListener('visibilitychange', function() { + if (document$1['visibilityState'] === 'hidden') { + flush_on_unload(); + } + }); + } + } + } + + this['persistence'] = this['cookie'] = new MixpanelPersistence(this['config']); + this.unpersisted_superprops = {}; + this._gdpr_init(); + + var uuid = _.UUID(); + if (!this.get_distinct_id()) { + // There is no need to set the distinct id + // or the device id if something was already stored + // in the persitence + this.register_once({ + 'distinct_id': DEVICE_ID_PREFIX + uuid, + '$device_id': uuid + }, ''); + } + + if (this.get_config('track_pageview')) { + this.track_pageview(); + } +}; + +// Private methods + +MixpanelLib.prototype._loaded = function() { + this.get_config('loaded')(this); + this._set_default_superprops(); +}; + +// update persistence with info on referrer, UTM params, etc +MixpanelLib.prototype._set_default_superprops = function() { + this['persistence'].update_search_keyword(document$1.referrer); + if (this.get_config('store_google')) { + this.register(_.info.campaignParams(), {persistent: false}); + } + if (this.get_config('save_referrer')) { + this['persistence'].update_referrer_info(document$1.referrer); + } +}; + +MixpanelLib.prototype._dom_loaded = function() { + _.each(this.__dom_loaded_queue, function(item) { + this._track_dom.apply(this, item); + }, this); + + if (!this.has_opted_out_tracking()) { + _.each(this.__request_queue, function(item) { + this._send_request.apply(this, item); + }, this); + } + + delete this.__dom_loaded_queue; + delete this.__request_queue; +}; + +MixpanelLib.prototype._track_dom = function(DomClass, args) { + if (this.get_config('img')) { + this.report_error('You can\'t use DOM tracking functions with img = true.'); + return false; + } + + if (!DOM_LOADED) { + this.__dom_loaded_queue.push([DomClass, args]); + return false; + } + + var dt = new DomClass().init(this); + return dt.track.apply(dt, args); +}; + +/** + * _prepare_callback() should be called by callers of _send_request for use + * as the callback argument. + * + * If there is no callback, this returns null. + * If we are going to make XHR/XDR requests, this returns a function. + * If we are going to use script tags, this returns a string to use as the + * callback GET param. + */ +MixpanelLib.prototype._prepare_callback = function(callback, data) { + if (_.isUndefined(callback)) { + return null; + } + + if (USE_XHR) { + var callback_function = function(response) { + callback(response, data); + }; + return callback_function; + } else { + // if the user gives us a callback, we store as a random + // property on this instances jsc function and update our + // callback string to reflect that. + var jsc = this['_jsc']; + var randomized_cb = '' + Math.floor(Math.random() * 100000000); + var callback_string = this.get_config('callback_fn') + '[' + randomized_cb + ']'; + jsc[randomized_cb] = function(response) { + delete jsc[randomized_cb]; + callback(response, data); + }; + return callback_string; + } +}; + +MixpanelLib.prototype._send_request = function(url, data, options, callback) { + var succeeded = true; + + if (ENQUEUE_REQUESTS) { + this.__request_queue.push(arguments); + return succeeded; + } + + var DEFAULT_OPTIONS = { + method: this.get_config('api_method'), + transport: this.get_config('api_transport'), + verbose: this.get_config('verbose') + }; + var body_data = null; + + if (!callback && (_.isFunction(options) || typeof options === 'string')) { + callback = options; + options = null; + } + options = _.extend(DEFAULT_OPTIONS, options || {}); + if (!USE_XHR) { + options.method = 'GET'; + } + var use_post = options.method === 'POST'; + var use_sendBeacon = sendBeacon && use_post && options.transport.toLowerCase() === 'sendbeacon'; + + // needed to correctly format responses + var verbose_mode = options.verbose; + if (data['verbose']) { verbose_mode = true; } + + if (this.get_config('test')) { data['test'] = 1; } + if (verbose_mode) { data['verbose'] = 1; } + if (this.get_config('img')) { data['img'] = 1; } + if (!USE_XHR) { + if (callback) { + data['callback'] = callback; + } else if (verbose_mode || this.get_config('test')) { + // Verbose output (from verbose mode, or an error in test mode) is a json blob, + // which by itself is not valid javascript. Without a callback, this verbose output will + // cause an error when returned via jsonp, so we force a no-op callback param. + // See the ECMA script spec: http://www.ecma-international.org/ecma-262/5.1/#sec-12.4 + data['callback'] = '(function(){})'; + } + } + + data['ip'] = this.get_config('ip')?1:0; + data['_'] = new Date().getTime().toString(); + + if (use_post) { + body_data = 'data=' + encodeURIComponent(data['data']); + delete data['data']; + } + + url += '?' + _.HTTPBuildQuery(data); + + var lib = this; + if ('img' in data) { + var img = document$1.createElement('img'); + img.src = url; + document$1.body.appendChild(img); + } else if (use_sendBeacon) { + try { + succeeded = sendBeacon(url, body_data); + } catch (e) { + lib.report_error(e); + succeeded = false; + } + try { + if (callback) { + callback(succeeded ? 1 : 0); + } + } catch (e) { + lib.report_error(e); + } + } else if (USE_XHR) { + try { + var req = new XMLHttpRequest(); + req.open(options.method, url, true); + + var headers = this.get_config('xhr_headers'); + if (use_post) { + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } + _.each(headers, function(headerValue, headerName) { + req.setRequestHeader(headerName, headerValue); + }); + + if (options.timeout_ms && typeof req.timeout !== 'undefined') { + req.timeout = options.timeout_ms; + var start_time = new Date().getTime(); + } + + // send the mp_optout cookie + // withCredentials cannot be modified until after calling .open on Android and Mobile Safari + req.withCredentials = true; + req.onreadystatechange = function () { + if (req.readyState === 4) { // XMLHttpRequest.DONE == 4, except in safari 4 + if (req.status === 200) { + if (callback) { + if (verbose_mode) { + var response; + try { + response = _.JSONDecode(req.responseText); + } catch (e) { + lib.report_error(e); + if (options.ignore_json_errors) { + response = req.responseText; + } else { + return; + } + } + callback(response); + } else { + callback(Number(req.responseText)); + } + } + } else { + var error; + if ( + req.timeout && + !req.status && + new Date().getTime() - start_time >= req.timeout + ) { + error = 'timeout'; + } else { + error = 'Bad HTTP status: ' + req.status + ' ' + req.statusText; + } + lib.report_error(error); + if (callback) { + if (verbose_mode) { + callback({status: 0, error: error, xhr_req: req}); + } else { + callback(0); + } + } + } + } + }; + req.send(body_data); + } catch (e) { + lib.report_error(e); + succeeded = false; + } + } else { + var script = document$1.createElement('script'); + script.type = 'text/javascript'; + script.async = true; + script.defer = true; + script.src = url; + var s = document$1.getElementsByTagName('script')[0]; + s.parentNode.insertBefore(script, s); + } + + return succeeded; +}; + +/** + * _execute_array() deals with processing any mixpanel function + * calls that were called before the Mixpanel library were loaded + * (and are thus stored in an array so they can be called later) + * + * Note: we fire off all the mixpanel function calls && user defined + * functions BEFORE we fire off mixpanel tracking calls. This is so + * identify/register/set_config calls can properly modify early + * tracking calls. + * + * @param {Array} array + */ +MixpanelLib.prototype._execute_array = function(array) { + var fn_name, alias_calls = [], other_calls = [], tracking_calls = []; + _.each(array, function(item) { + if (item) { + fn_name = item[0]; + if (_.isArray(fn_name)) { + tracking_calls.push(item); // chained call e.g. mixpanel.get_group().set() + } else if (typeof(item) === 'function') { + item.call(this); + } else if (_.isArray(item) && fn_name === 'alias') { + alias_calls.push(item); + } else if (_.isArray(item) && fn_name.indexOf('track') !== -1 && typeof(this[fn_name]) === 'function') { + tracking_calls.push(item); + } else { + other_calls.push(item); + } + } + }, this); + + var execute = function(calls, context) { + _.each(calls, function(item) { + if (_.isArray(item[0])) { + // chained call + var caller = context; + _.each(item, function(call) { + caller = caller[call[0]].apply(caller, call.slice(1)); + }); + } else { + this[item[0]].apply(this, item.slice(1)); + } + }, context); + }; + + execute(alias_calls, this); + execute(other_calls, this); + execute(tracking_calls, this); +}; + +// request queueing utils + +MixpanelLib.prototype.are_batchers_initialized = function() { + return !!this.request_batchers.events; +}; + +MixpanelLib.prototype.init_batchers = function() { + var token = this.get_config('token'); + if (!this.are_batchers_initialized()) { + var batcher_for = _.bind(function(attrs) { + return new RequestBatcher( + '__mpq_' + token + attrs.queue_suffix, + { + libConfig: this['config'], + sendRequestFunc: _.bind(function(data, options, cb) { + this._send_request( + this.get_config('api_host') + attrs.endpoint, + this._encode_data_for_request(data), + options, + this._prepare_callback(cb, data) + ); + }, this), + beforeSendHook: _.bind(function(item) { + return this._run_hook('before_send_' + attrs.type, item); + }, this), + errorReporter: this.get_config('error_reporter'), + stopAllBatchingFunc: _.bind(this.stop_batch_senders, this) + } + ); + }, this); + this.request_batchers = { + events: batcher_for({type: 'events', endpoint: '/track/', queue_suffix: '_ev'}), + people: batcher_for({type: 'people', endpoint: '/engage/', queue_suffix: '_pp'}), + groups: batcher_for({type: 'groups', endpoint: '/groups/', queue_suffix: '_gr'}) + }; + } + if (this.get_config('batch_autostart')) { + this.start_batch_senders(); + } +}; + +MixpanelLib.prototype.start_batch_senders = function() { + if (this.are_batchers_initialized()) { + this._batch_requests = true; + _.each(this.request_batchers, function(batcher) { + batcher.start(); + }); + } +}; + +MixpanelLib.prototype.stop_batch_senders = function() { + this._batch_requests = false; + _.each(this.request_batchers, function(batcher) { + batcher.stop(); + batcher.clear(); + }); +}; + +/** + * push() keeps the standard async-array-push + * behavior around after the lib is loaded. + * This is only useful for external integrations that + * do not wish to rely on our convenience methods + * (created in the snippet). + * + * ### Usage: + * mixpanel.push(['register', { a: 'b' }]); + * + * @param {Array} item A [function_name, args...] array to be executed + */ +MixpanelLib.prototype.push = function(item) { + this._execute_array([item]); +}; + +/** + * Disable events on the Mixpanel object. If passed no arguments, + * this function disables tracking of any event. If passed an + * array of event names, those events will be disabled, but other + * events will continue to be tracked. + * + * Note: this function does not stop other mixpanel functions from + * firing, such as register() or people.set(). + * + * @param {Array} [events] An array of event names to disable + */ +MixpanelLib.prototype.disable = function(events) { + if (typeof(events) === 'undefined') { + this._flags.disable_all_events = true; + } else { + this.__disabled_events = this.__disabled_events.concat(events); + } +}; + +MixpanelLib.prototype._encode_data_for_request = function(data) { + var encoded_data = _.JSONEncode(data); + if (this.get_config('api_payload_format') === PAYLOAD_TYPE_BASE64) { + encoded_data = _.base64Encode(encoded_data); + } + return {'data': encoded_data}; +}; + +// internal method for handling track vs batch-enqueue logic +MixpanelLib.prototype._track_or_batch = function(options, callback) { + var truncated_data = _.truncate(options.data, 255); + var endpoint = options.endpoint; + var batcher = options.batcher; + var should_send_immediately = options.should_send_immediately; + var send_request_options = options.send_request_options || {}; + callback = callback || NOOP_FUNC; + + var request_enqueued_or_initiated = true; + var send_request_immediately = _.bind(function() { + if (!send_request_options.skip_hooks) { + truncated_data = this._run_hook('before_send_' + options.type, truncated_data); + } + if (truncated_data) { + console$1.log('MIXPANEL REQUEST:'); + console$1.log(truncated_data); + return this._send_request( + endpoint, + this._encode_data_for_request(truncated_data), + send_request_options, + this._prepare_callback(callback, truncated_data) + ); + } else { + return null; + } + }, this); + + if (this._batch_requests && !should_send_immediately) { + batcher.enqueue(truncated_data, function(succeeded) { + if (succeeded) { + callback(1, truncated_data); + } else { + send_request_immediately(); + } + }); + } else { + request_enqueued_or_initiated = send_request_immediately(); + } + + return request_enqueued_or_initiated && truncated_data; +}; + +/** + * Track an event. This is the most important and + * frequently used Mixpanel function. + * + * ### Usage: + * + * // track an event named 'Registered' + * mixpanel.track('Registered', {'Gender': 'Male', 'Age': 21}); + * + * // track an event using navigator.sendBeacon + * mixpanel.track('Left page', {'duration_seconds': 35}, {transport: 'sendBeacon'}); + * + * To track link clicks or form submissions, see track_links() or track_forms(). + * + * @param {String} event_name The name of the event. This can be anything the user does - 'Button Click', 'Sign Up', 'Item Purchased', etc. + * @param {Object} [properties] A set of properties to include with the event you're sending. These describe the user who did the event or details about the event itself. + * @param {Object} [options] Optional configuration for this track request. + * @param {String} [options.transport] Transport method for network request ('xhr' or 'sendBeacon'). + * @param {Boolean} [options.send_immediately] Whether to bypass batching/queueing and send track request immediately. + * @param {Function} [callback] If provided, the callback function will be called after tracking the event. + * @returns {Boolean|Object} If the tracking request was successfully initiated/queued, an object + * with the tracking payload sent to the API server is returned; otherwise false. + */ +MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, properties, options, callback) { + if (!callback && typeof options === 'function') { + callback = options; + options = null; + } + options = options || {}; + var transport = options['transport']; // external API, don't minify 'transport' prop + if (transport) { + options.transport = transport; // 'transport' prop name can be minified internally + } + var should_send_immediately = options['send_immediately']; + if (typeof callback !== 'function') { + callback = NOOP_FUNC; + } + + if (_.isUndefined(event_name)) { + this.report_error('No event name provided to mixpanel.track'); + return; + } + + if (this._event_is_disabled(event_name)) { + callback(0); + return; + } + + // set defaults + properties = properties || {}; + properties['token'] = this.get_config('token'); + + // set $duration if time_event was previously called for this event + var start_timestamp = this['persistence'].remove_event_timer(event_name); + if (!_.isUndefined(start_timestamp)) { + var duration_in_ms = new Date().getTime() - start_timestamp; + properties['$duration'] = parseFloat((duration_in_ms / 1000).toFixed(3)); + } + + this._set_default_superprops(); + + var marketing_properties = this.get_config('track_marketing') + ? _.info.marketingParams() + : {}; + + // note: extend writes to the first object, so lets make sure we + // don't write to the persistence properties object and info + // properties object by passing in a new object + + // update properties with pageview info and super-properties + properties = _.extend( + {}, + _.info.properties(), + marketing_properties, + this['persistence'].properties(), + this.unpersisted_superprops, + properties + ); + + var property_blacklist = this.get_config('property_blacklist'); + if (_.isArray(property_blacklist)) { + _.each(property_blacklist, function(blacklisted_prop) { + delete properties[blacklisted_prop]; + }); + } else { + this.report_error('Invalid value for property_blacklist config: ' + property_blacklist); + } + + var data = { + 'event': event_name, + 'properties': properties + }; + var ret = this._track_or_batch({ + type: 'events', + data: data, + endpoint: this.get_config('api_host') + '/track/', + batcher: this.request_batchers.events, + should_send_immediately: should_send_immediately, + send_request_options: options + }, callback); + + return ret; +}); + +/** + * Register the current user into one/many groups. + * + * ### Usage: + * + * mixpanel.set_group('company', ['mixpanel', 'google']) // an array of IDs + * mixpanel.set_group('company', 'mixpanel') + * mixpanel.set_group('company', 128746312) + * + * @param {String} group_key Group key + * @param {Array|String|Number} group_ids An array of group IDs, or a singular group ID + * @param {Function} [callback] If provided, the callback will be called after tracking the event. + * + */ +MixpanelLib.prototype.set_group = addOptOutCheckMixpanelLib(function(group_key, group_ids, callback) { + if (!_.isArray(group_ids)) { + group_ids = [group_ids]; + } + var prop = {}; + prop[group_key] = group_ids; + this.register(prop); + return this['people'].set(group_key, group_ids, callback); +}); + +/** + * Add a new group for this user. + * + * ### Usage: + * + * mixpanel.add_group('company', 'mixpanel') + * + * @param {String} group_key Group key + * @param {*} group_id A valid Mixpanel property type + * @param {Function} [callback] If provided, the callback will be called after tracking the event. + */ +MixpanelLib.prototype.add_group = addOptOutCheckMixpanelLib(function(group_key, group_id, callback) { + var old_values = this.get_property(group_key); + if (old_values === undefined) { + var prop = {}; + prop[group_key] = [group_id]; + this.register(prop); + } else { + if (old_values.indexOf(group_id) === -1) { + old_values.push(group_id); + this.register(prop); + } + } + return this['people'].union(group_key, group_id, callback); +}); + +/** + * Remove a group from this user. + * + * ### Usage: + * + * mixpanel.remove_group('company', 'mixpanel') + * + * @param {String} group_key Group key + * @param {*} group_id A valid Mixpanel property type + * @param {Function} [callback] If provided, the callback will be called after tracking the event. + */ +MixpanelLib.prototype.remove_group = addOptOutCheckMixpanelLib(function(group_key, group_id, callback) { + var old_value = this.get_property(group_key); + // if the value doesn't exist, the persistent store is unchanged + if (old_value !== undefined) { + var idx = old_value.indexOf(group_id); + if (idx > -1) { + old_value.splice(idx, 1); + this.register({group_key: old_value}); + } + if (old_value.length === 0) { + this.unregister(group_key); + } + } + return this['people'].remove(group_key, group_id, callback); +}); + +/** + * Track an event with specific groups. + * + * ### Usage: + * + * mixpanel.track_with_groups('purchase', {'product': 'iphone'}, {'University': ['UCB', 'UCLA']}) + * + * @param {String} event_name The name of the event (see `mixpanel.track()`) + * @param {Object=} properties A set of properties to include with the event you're sending (see `mixpanel.track()`) + * @param {Object=} groups An object mapping group name keys to one or more values + * @param {Function} [callback] If provided, the callback will be called after tracking the event. + */ +MixpanelLib.prototype.track_with_groups = addOptOutCheckMixpanelLib(function(event_name, properties, groups, callback) { + var tracking_props = _.extend({}, properties || {}); + _.each(groups, function(v, k) { + if (v !== null && v !== undefined) { + tracking_props[k] = v; + } + }); + return this.track(event_name, tracking_props, callback); +}); + +MixpanelLib.prototype._create_map_key = function (group_key, group_id) { + return group_key + '_' + JSON.stringify(group_id); +}; + +MixpanelLib.prototype._remove_group_from_cache = function (group_key, group_id) { + delete this._cached_groups[this._create_map_key(group_key, group_id)]; +}; + +/** + * Look up reference to a Mixpanel group + * + * ### Usage: + * + * mixpanel.get_group(group_key, group_id) + * + * @param {String} group_key Group key + * @param {Object} group_id A valid Mixpanel property type + * @returns {Object} A MixpanelGroup identifier + */ +MixpanelLib.prototype.get_group = function (group_key, group_id) { + var map_key = this._create_map_key(group_key, group_id); + var group = this._cached_groups[map_key]; + if (group === undefined || group._group_key !== group_key || group._group_id !== group_id) { + group = new MixpanelGroup(); + group._init(this, group_key, group_id); + this._cached_groups[map_key] = group; + } + return group; +}; + +/** + * Track a default Mixpanel page view event, which includes extra default event properties to + * improve page view data. The `config.track_pageview` option for mixpanel.init() + * may be turned on for tracking page loads automatically. + * + * ### Usage + * + * // track a default $mp_web_page_view event + * mixpanel.track_pageview(); + * + * // track a page view event with additional event properties + * mixpanel.track_pageview({'ab_test_variant': 'card-layout-b'}); + * + * // example approach to track page views on different page types as event properties + * mixpanel.track_pageview({'page': 'pricing'}); + * mixpanel.track_pageview({'page': 'homepage'}); + * + * // UNCOMMON: Tracking a page view event with a custom event_name option. NOT expected to be used for + * // individual pages on the same site or product. Use cases for custom event_name may be page + * // views on different products or internal applications that are considered completely separate + * mixpanel.track_pageview({'page': 'customer-search'}, {'event_name': '[internal] Admin Page View'}); + * + * @param {Object} [properties] An optional set of additional properties to send with the page view event + * @param {Object} [options] Page view tracking options + * @param {String} [options.event_name] - Alternate name for the tracking event + * @returns {Boolean|Object} If the tracking request was successfully initiated/queued, an object + * with the tracking payload sent to the API server is returned; otherwise false. + */ +MixpanelLib.prototype.track_pageview = addOptOutCheckMixpanelLib(function(properties, options) { + if (typeof properties !== 'object') { + properties = {}; + } + options = options || {}; + var event_name = options['event_name'] || '$mp_web_page_view'; + + var default_page_properties = _.extend( + _.info.mpPageViewProperties(), + _.info.campaignParams(), + _.info.clickParams() + ); + + var event_properties = _.extend( + {}, + default_page_properties, + properties + ); + + return this.track(event_name, event_properties); +}); + +/** + * Track clicks on a set of document elements. Selector must be a + * valid query. Elements must exist on the page at the time track_links is called. + * + * ### Usage: + * + * // track click for link id #nav + * mixpanel.track_links('#nav', 'Clicked Nav Link'); + * + * ### Notes: + * + * This function will wait up to 300 ms for the Mixpanel + * servers to respond. If they have not responded by that time + * it will head to the link without ensuring that your event + * has been tracked. To configure this timeout please see the + * set_config() documentation below. + * + * If you pass a function in as the properties argument, the + * function will receive the DOMElement that triggered the + * event as an argument. You are expected to return an object + * from the function; any properties defined on this object + * will be sent to mixpanel as event properties. + * + * @type {Function} + * @param {Object|String} query A valid DOM query, element or jQuery-esque list + * @param {String} event_name The name of the event to track + * @param {Object|Function} [properties] A properties object or function that returns a dictionary of properties when passed a DOMElement + */ +MixpanelLib.prototype.track_links = function() { + return this._track_dom.call(this, LinkTracker, arguments); +}; + +/** + * Track form submissions. Selector must be a valid query. + * + * ### Usage: + * + * // track submission for form id 'register' + * mixpanel.track_forms('#register', 'Created Account'); + * + * ### Notes: + * + * This function will wait up to 300 ms for the mixpanel + * servers to respond, if they have not responded by that time + * it will head to the link without ensuring that your event + * has been tracked. To configure this timeout please see the + * set_config() documentation below. + * + * If you pass a function in as the properties argument, the + * function will receive the DOMElement that triggered the + * event as an argument. You are expected to return an object + * from the function; any properties defined on this object + * will be sent to mixpanel as event properties. + * + * @type {Function} + * @param {Object|String} query A valid DOM query, element or jQuery-esque list + * @param {String} event_name The name of the event to track + * @param {Object|Function} [properties] This can be a set of properties, or a function that returns a set of properties after being passed a DOMElement + */ +MixpanelLib.prototype.track_forms = function() { + return this._track_dom.call(this, FormTracker, arguments); +}; + +/** + * Time an event by including the time between this call and a + * later 'track' call for the same event in the properties sent + * with the event. + * + * ### Usage: + * + * // time an event named 'Registered' + * mixpanel.time_event('Registered'); + * mixpanel.track('Registered', {'Gender': 'Male', 'Age': 21}); + * + * When called for a particular event name, the next track call for that event + * name will include the elapsed time between the 'time_event' and 'track' + * calls. This value is stored as seconds in the '$duration' property. + * + * @param {String} event_name The name of the event. + */ +MixpanelLib.prototype.time_event = function(event_name) { + if (_.isUndefined(event_name)) { + this.report_error('No event name provided to mixpanel.time_event'); + return; + } + + if (this._event_is_disabled(event_name)) { + return; + } + + this['persistence'].set_event_timer(event_name, new Date().getTime()); +}; + +var REGISTER_DEFAULTS = { + 'persistent': true +}; +/** + * Helper to parse options param for register methods, maintaining + * legacy support for plain "days" param instead of options object + * @param {Number|Object} [days_or_options] 'days' option (Number), or Options object for register methods + * @returns {Object} options object + */ +var options_for_register = function(days_or_options) { + var options; + if (_.isObject(days_or_options)) { + options = days_or_options; + } else if (!_.isUndefined(days_or_options)) { + options = {'days': days_or_options}; + } else { + options = {}; + } + return _.extend({}, REGISTER_DEFAULTS, options); +}; + +/** + * Register a set of super properties, which are included with all + * events. This will overwrite previous super property values. + * + * ### Usage: + * + * // register 'Gender' as a super property + * mixpanel.register({'Gender': 'Female'}); + * + * // register several super properties when a user signs up + * mixpanel.register({ + * 'Email': 'jdoe@example.com', + * 'Account Type': 'Free' + * }); + * + * // register only for the current pageload + * mixpanel.register({'Name': 'Pat'}, {persistent: false}); + * + * @param {Object} properties An associative array of properties to store about the user + * @param {Number|Object} [days_or_options] Options object or number of days since the user's last visit to store the super properties (only valid for persisted props) + * @param {boolean} [days_or_options.days] - number of days since the user's last visit to store the super properties (only valid for persisted props) + * @param {boolean} [days_or_options.persistent=true] - whether to put in persistent storage (cookie/localStorage) + */ +MixpanelLib.prototype.register = function(props, days_or_options) { + var options = options_for_register(days_or_options); + if (options['persistent']) { + this['persistence'].register(props, options['days']); + } else { + _.extend(this.unpersisted_superprops, props); + } +}; + +/** + * Register a set of super properties only once. This will not + * overwrite previous super property values, unlike register(). + * + * ### Usage: + * + * // register a super property for the first time only + * mixpanel.register_once({ + * 'First Login Date': new Date().toISOString() + * }); + * + * // register once, only for the current pageload + * mixpanel.register_once({ + * 'First interaction time': new Date().toISOString() + * }, 'None', {persistent: false}); + * + * ### Notes: + * + * If default_value is specified, current super properties + * with that value will be overwritten. + * + * @param {Object} properties An associative array of properties to store about the user + * @param {*} [default_value] Value to override if already set in super properties (ex: 'False') Default: 'None' + * @param {Number|Object} [days_or_options] Options object or number of days since the user's last visit to store the super properties (only valid for persisted props) + * @param {boolean} [days_or_options.days] - number of days since the user's last visit to store the super properties (only valid for persisted props) + * @param {boolean} [days_or_options.persistent=true] - whether to put in persistent storage (cookie/localStorage) + */ +MixpanelLib.prototype.register_once = function(props, default_value, days_or_options) { + var options = options_for_register(days_or_options); + if (options['persistent']) { + this['persistence'].register_once(props, default_value, options['days']); + } else { + if (typeof(default_value) === 'undefined') { + default_value = 'None'; + } + _.each(props, function(val, prop) { + if (!this.unpersisted_superprops.hasOwnProperty(prop) || this.unpersisted_superprops[prop] === default_value) { + this.unpersisted_superprops[prop] = val; + } + }, this); + } +}; + +/** + * Delete a super property stored with the current user. + * + * @param {String} property The name of the super property to remove + * @param {Object} [options] + * @param {boolean} [options.persistent=true] - whether to look in persistent storage (cookie/localStorage) + */ +MixpanelLib.prototype.unregister = function(property, options) { + options = options_for_register(options); + if (options['persistent']) { + this['persistence'].unregister(property); + } else { + delete this.unpersisted_superprops[property]; + } +}; + +MixpanelLib.prototype._register_single = function(prop, value) { + var props = {}; + props[prop] = value; + this.register(props); +}; + +/** + * Identify a user with a unique ID to track user activity across + * devices, tie a user to their events, and create a user profile. + * If you never call this method, unique visitors are tracked using + * a UUID generated the first time they visit the site. + * + * Call identify when you know the identity of the current user, + * typically after login or signup. We recommend against using + * identify for anonymous visitors to your site. + * + * ### Notes: + * If your project has + * ID Merge + * enabled, the identify method will connect pre- and + * post-authentication events when appropriate. + * + * If your project does not have ID Merge enabled, identify will + * change the user's local distinct_id to the unique ID you pass. + * Events tracked prior to authentication will not be connected + * to the same user identity. If ID Merge is disabled, alias can + * be used to connect pre- and post-registration events. + * + * @param {String} [unique_id] A string that uniquely identifies a user. If not provided, the distinct_id currently in the persistent store (cookie or localStorage) will be used. + */ +MixpanelLib.prototype.identify = function( + new_distinct_id, _set_callback, _add_callback, _append_callback, _set_once_callback, _union_callback, _unset_callback, _remove_callback +) { + // Optional Parameters + // _set_callback:function A callback to be run if and when the People set queue is flushed + // _add_callback:function A callback to be run if and when the People add queue is flushed + // _append_callback:function A callback to be run if and when the People append queue is flushed + // _set_once_callback:function A callback to be run if and when the People set_once queue is flushed + // _union_callback:function A callback to be run if and when the People union queue is flushed + // _unset_callback:function A callback to be run if and when the People unset queue is flushed + + var previous_distinct_id = this.get_distinct_id(); + if (new_distinct_id && previous_distinct_id !== new_distinct_id) { + // we allow the following condition if previous distinct_id is same as new_distinct_id + // so that you can force flush people updates for anonymous profiles. + if (typeof new_distinct_id === 'string' && new_distinct_id.indexOf(DEVICE_ID_PREFIX) === 0) { + this.report_error('distinct_id cannot have $device: prefix'); + return -1; + } + this.register({'$user_id': new_distinct_id}); + } + + if (!this.get_property('$device_id')) { + // The persisted distinct id might not actually be a device id at all + // it might be a distinct id of the user from before + var device_id = previous_distinct_id; + this.register_once({ + '$had_persisted_distinct_id': true, + '$device_id': device_id + }, ''); + } + + // identify only changes the distinct id if it doesn't match either the existing or the alias; + // if it's new, blow away the alias as well. + if (new_distinct_id !== previous_distinct_id && new_distinct_id !== this.get_property(ALIAS_ID_KEY)) { + this.unregister(ALIAS_ID_KEY); + this.register({'distinct_id': new_distinct_id}); + } + this._flags.identify_called = true; + // Flush any queued up people requests + this['people']._flush(_set_callback, _add_callback, _append_callback, _set_once_callback, _union_callback, _unset_callback, _remove_callback); + + // send an $identify event any time the distinct_id is changing - logic on the server + // will determine whether or not to do anything with it. + if (new_distinct_id !== previous_distinct_id) { + this.track('$identify', { + 'distinct_id': new_distinct_id, + '$anon_distinct_id': previous_distinct_id + }, {skip_hooks: true}); + } +}; + +/** + * Clears super properties and generates a new random distinct_id for this instance. + * Useful for clearing data when a user logs out. + */ +MixpanelLib.prototype.reset = function() { + this['persistence'].clear(); + this._flags.identify_called = false; + var uuid = _.UUID(); + this.register_once({ + 'distinct_id': DEVICE_ID_PREFIX + uuid, + '$device_id': uuid + }, ''); +}; + +/** + * Returns the current distinct id of the user. This is either the id automatically + * generated by the library or the id that has been passed by a call to identify(). + * + * ### Notes: + * + * get_distinct_id() can only be called after the Mixpanel library has finished loading. + * init() has a loaded function available to handle this automatically. For example: + * + * // set distinct_id after the mixpanel library has loaded + * mixpanel.init('YOUR PROJECT TOKEN', { + * loaded: function(mixpanel) { + * distinct_id = mixpanel.get_distinct_id(); + * } + * }); + */ +MixpanelLib.prototype.get_distinct_id = function() { + return this.get_property('distinct_id'); +}; + +/** + * The alias method creates an alias which Mixpanel will use to + * remap one id to another. Multiple aliases can point to the + * same identifier. + * + * The following is a valid use of alias: + * + * mixpanel.alias('new_id', 'existing_id'); + * // You can add multiple id aliases to the existing ID + * mixpanel.alias('newer_id', 'existing_id'); + * + * Aliases can also be chained - the following is a valid example: + * + * mixpanel.alias('new_id', 'existing_id'); + * // chain newer_id - new_id - existing_id + * mixpanel.alias('newer_id', 'new_id'); + * + * Aliases cannot point to multiple identifiers - the following + * example will not work: + * + * mixpanel.alias('new_id', 'existing_id'); + * // this is invalid as 'new_id' already points to 'existing_id' + * mixpanel.alias('new_id', 'newer_id'); + * + * ### Notes: + * + * If your project does not have + * ID Merge + * enabled, the best practice is to call alias once when a unique + * ID is first created for a user (e.g., when a user first registers + * for an account). Do not use alias multiple times for a single + * user without ID Merge enabled. + * + * @param {String} alias A unique identifier that you want to use for this user in the future. + * @param {String} [original] The current identifier being used for this user. + */ +MixpanelLib.prototype.alias = function(alias, original) { + // If the $people_distinct_id key exists in persistence, there has been a previous + // mixpanel.people.identify() call made for this user. It is VERY BAD to make an alias with + // this ID, as it will duplicate users. + if (alias === this.get_property(PEOPLE_DISTINCT_ID_KEY)) { + this.report_error('Attempting to create alias for existing People user - aborting.'); + return -2; + } + + var _this = this; + if (_.isUndefined(original)) { + original = this.get_distinct_id(); + } + if (alias !== original) { + this._register_single(ALIAS_ID_KEY, alias); + return this.track('$create_alias', { + 'alias': alias, + 'distinct_id': original + }, { + skip_hooks: true + }, function() { + // Flush the people queue + _this.identify(alias); + }); + } else { + this.report_error('alias matches current distinct_id - skipping api call.'); + this.identify(alias); + return -1; + } +}; + +/** + * Provide a string to recognize the user by. The string passed to + * this method will appear in the Mixpanel Streams product rather + * than an automatically generated name. Name tags do not have to + * be unique. + * + * This value will only be included in Streams data. + * + * @param {String} name_tag A human readable name for the user + * @deprecated + */ +MixpanelLib.prototype.name_tag = function(name_tag) { + this._register_single('mp_name_tag', name_tag); +}; + +/** + * Update the configuration of a mixpanel library instance. + * + * The default config is: + * + * { + * // HTTP method for tracking requests + * api_method: 'POST' + * + * // transport for sending requests ('XHR' or 'sendBeacon') + * // NB: sendBeacon should only be used for scenarios such as + * // page unload where a "best-effort" attempt to send is + * // acceptable; the sendBeacon API does not support callbacks + * // or any way to know the result of the request. Mixpanel + * // tracking via sendBeacon will not support any event- + * // batching or retry mechanisms. + * api_transport: 'XHR' + * + * // request-batching/queueing/retry + * batch_requests: true, + * + * // maximum number of events/updates to send in a single + * // network request + * batch_size: 50, + * + * // milliseconds to wait between sending batch requests + * batch_flush_interval_ms: 5000, + * + * // milliseconds to wait for network responses to batch requests + * // before they are considered timed-out and retried + * batch_request_timeout_ms: 90000, + * + * // override value for cookie domain, only useful for ensuring + * // correct cross-subdomain cookies on unusual domains like + * // subdomain.mainsite.avocat.fr; NB this cannot be used to + * // set cookies on a different domain than the current origin + * cookie_domain: '' + * + * // super properties cookie expiration (in days) + * cookie_expiration: 365 + * + * // if true, cookie will be set with SameSite=None; Secure + * // this is only useful in special situations, like embedded + * // 3rd-party iframes that set up a Mixpanel instance + * cross_site_cookie: false + * + * // super properties span subdomains + * cross_subdomain_cookie: true + * + * // debug mode + * debug: false + * + * // if this is true, the mixpanel cookie or localStorage entry + * // will be deleted, and no user persistence will take place + * disable_persistence: false + * + * // if this is true, Mixpanel will automatically determine + * // City, Region and Country data using the IP address of + * //the client + * ip: true + * + * // opt users out of tracking by this Mixpanel instance by default + * opt_out_tracking_by_default: false + * + * // opt users out of browser data storage by this Mixpanel instance by default + * opt_out_persistence_by_default: false + * + * // persistence mechanism used by opt-in/opt-out methods - cookie + * // or localStorage - falls back to cookie if localStorage is unavailable + * opt_out_tracking_persistence_type: 'localStorage' + * + * // customize the name of cookie/localStorage set by opt-in/opt-out methods + * opt_out_tracking_cookie_prefix: null + * + * // type of persistent store for super properties (cookie/ + * // localStorage) if set to 'localStorage', any existing + * // mixpanel cookie value with the same persistence_name + * // will be transferred to localStorage and deleted + * persistence: 'cookie' + * + * // name for super properties persistent store + * persistence_name: '' + * + * // names of properties/superproperties which should never + * // be sent with track() calls + * property_blacklist: [] + * + * // if this is true, mixpanel cookies will be marked as + * // secure, meaning they will only be transmitted over https + * secure_cookie: false + * + * // disables enriching user profiles with first touch marketing data + * skip_first_touch_marketing: false + * + * // the amount of time track_links will + * // wait for Mixpanel's servers to respond + * track_links_timeout: 300 + * + * // adds any UTM parameters and click IDs present on the page to any events fired + * track_marketing: true + * + * // enables automatic page view tracking using default page view events through + * // the track_pageview() method + * track_pageview: false + * + * // if you set upgrade to be true, the library will check for + * // a cookie from our old js library and import super + * // properties from it, then the old cookie is deleted + * // The upgrade config option only works in the initialization, + * // so make sure you set it when you create the library. + * upgrade: false + * + * // extra HTTP request headers to set for each API request, in + * // the format {'Header-Name': value} + * xhr_headers: {} + * + * // whether to ignore or respect the web browser's Do Not Track setting + * ignore_dnt: false + * } + * + * + * @param {Object} config A dictionary of new configuration values to update + */ +MixpanelLib.prototype.set_config = function(config) { + if (_.isObject(config)) { + _.extend(this['config'], config); + + var new_batch_size = config['batch_size']; + if (new_batch_size) { + _.each(this.request_batchers, function(batcher) { + batcher.resetBatchSize(); + }); + } + + if (!this.get_config('persistence_name')) { + this['config']['persistence_name'] = this['config']['cookie_name']; + } + if (!this.get_config('disable_persistence')) { + this['config']['disable_persistence'] = this['config']['disable_cookie']; + } + + if (this['persistence']) { + this['persistence'].update_config(this['config']); + } + Config.DEBUG = Config.DEBUG || this.get_config('debug'); + } +}; + +/** + * returns the current config object for the library. + */ +MixpanelLib.prototype.get_config = function(prop_name) { + return this['config'][prop_name]; +}; + +/** + * Fetch a hook function from config, with safe default, and run it + * against the given arguments + * @param {string} hook_name which hook to retrieve + * @returns {any|null} return value of user-provided hook, or null if nothing was returned + */ +MixpanelLib.prototype._run_hook = function(hook_name) { + var ret = (this['config']['hooks'][hook_name] || IDENTITY_FUNC).apply(this, slice.call(arguments, 1)); + if (typeof ret === 'undefined') { + this.report_error(hook_name + ' hook did not return a value'); + ret = null; + } + return ret; +}; + +/** + * Returns the value of the super property named property_name. If no such + * property is set, get_property() will return the undefined value. + * + * ### Notes: + * + * get_property() can only be called after the Mixpanel library has finished loading. + * init() has a loaded function available to handle this automatically. For example: + * + * // grab value for 'user_id' after the mixpanel library has loaded + * mixpanel.init('YOUR PROJECT TOKEN', { + * loaded: function(mixpanel) { + * user_id = mixpanel.get_property('user_id'); + * } + * }); + * + * @param {String} property_name The name of the super property you want to retrieve + */ +MixpanelLib.prototype.get_property = function(property_name) { + return this['persistence']['props'][property_name]; +}; + +MixpanelLib.prototype.toString = function() { + var name = this.get_config('name'); + if (name !== PRIMARY_INSTANCE_NAME) { + name = PRIMARY_INSTANCE_NAME + '.' + name; + } + return name; +}; + +MixpanelLib.prototype._event_is_disabled = function(event_name) { + return _.isBlockedUA(userAgent) || + this._flags.disable_all_events || + _.include(this.__disabled_events, event_name); +}; + +// perform some housekeeping around GDPR opt-in/out state +MixpanelLib.prototype._gdpr_init = function() { + var is_localStorage_requested = this.get_config('opt_out_tracking_persistence_type') === 'localStorage'; + + // try to convert opt-in/out cookies to localStorage if possible + if (is_localStorage_requested && _.localStorage.is_supported()) { + if (!this.has_opted_in_tracking() && this.has_opted_in_tracking({'persistence_type': 'cookie'})) { + this.opt_in_tracking({'enable_persistence': false}); + } + if (!this.has_opted_out_tracking() && this.has_opted_out_tracking({'persistence_type': 'cookie'})) { + this.opt_out_tracking({'clear_persistence': false}); + } + this.clear_opt_in_out_tracking({ + 'persistence_type': 'cookie', + 'enable_persistence': false + }); + } + + // check whether the user has already opted out - if so, clear & disable persistence + if (this.has_opted_out_tracking()) { + this._gdpr_update_persistence({'clear_persistence': true}); + + // check whether we should opt out by default + // note: we don't clear persistence here by default since opt-out default state is often + // used as an initial state while GDPR information is being collected + } else if (!this.has_opted_in_tracking() && ( + this.get_config('opt_out_tracking_by_default') || _.cookie.get('mp_optout') + )) { + _.cookie.remove('mp_optout'); + this.opt_out_tracking({ + 'clear_persistence': this.get_config('opt_out_persistence_by_default') + }); + } +}; + +/** + * Enable or disable persistence based on options + * only enable/disable if persistence is not already in this state + * @param {boolean} [options.clear_persistence] If true, will delete all data stored by the sdk in persistence and disable it + * @param {boolean} [options.enable_persistence] If true, will re-enable sdk persistence + */ +MixpanelLib.prototype._gdpr_update_persistence = function(options) { + var disabled; + if (options && options['clear_persistence']) { + disabled = true; + } else if (options && options['enable_persistence']) { + disabled = false; + } else { + return; + } + + if (!this.get_config('disable_persistence') && this['persistence'].disabled !== disabled) { + this['persistence'].set_disabled(disabled); + } + + if (disabled) { + _.each(this.request_batchers, function(batcher) { + batcher.clear(); + }); + } +}; + +// call a base gdpr function after constructing the appropriate token and options args +MixpanelLib.prototype._gdpr_call_func = function(func, options) { + options = _.extend({ + 'track': _.bind(this.track, this), + 'persistence_type': this.get_config('opt_out_tracking_persistence_type'), + 'cookie_prefix': this.get_config('opt_out_tracking_cookie_prefix'), + 'cookie_expiration': this.get_config('cookie_expiration'), + 'cross_site_cookie': this.get_config('cross_site_cookie'), + 'cross_subdomain_cookie': this.get_config('cross_subdomain_cookie'), + 'cookie_domain': this.get_config('cookie_domain'), + 'secure_cookie': this.get_config('secure_cookie'), + 'ignore_dnt': this.get_config('ignore_dnt') + }, options); + + // check if localStorage can be used for recording opt out status, fall back to cookie if not + if (!_.localStorage.is_supported()) { + options['persistence_type'] = 'cookie'; + } + + return func(this.get_config('token'), { + track: options['track'], + trackEventName: options['track_event_name'], + trackProperties: options['track_properties'], + persistenceType: options['persistence_type'], + persistencePrefix: options['cookie_prefix'], + cookieDomain: options['cookie_domain'], + cookieExpiration: options['cookie_expiration'], + crossSiteCookie: options['cross_site_cookie'], + crossSubdomainCookie: options['cross_subdomain_cookie'], + secureCookie: options['secure_cookie'], + ignoreDnt: options['ignore_dnt'] + }); +}; + +/** + * Opt the user in to data tracking and cookies/localstorage for this Mixpanel instance + * + * ### Usage + * + * // opt user in + * mixpanel.opt_in_tracking(); + * + * // opt user in with specific event name, properties, cookie configuration + * mixpanel.opt_in_tracking({ + * track_event_name: 'User opted in', + * track_event_properties: { + * 'Email': 'jdoe@example.com' + * }, + * cookie_expiration: 30, + * secure_cookie: true + * }); + * + * @param {Object} [options] A dictionary of config options to override + * @param {function} [options.track] Function used for tracking a Mixpanel event to record the opt-in action (default is this Mixpanel instance's track method) + * @param {string} [options.track_event_name=$opt_in] Event name to be used for tracking the opt-in action + * @param {Object} [options.track_properties] Set of properties to be tracked along with the opt-in action + * @param {boolean} [options.enable_persistence=true] If true, will re-enable sdk persistence + * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable + * @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this Mixpanel instance's config) + * @param {string} [options.cookie_domain] Custom cookie domain (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.cross_site_cookie] Whether the opt-in cookie is set as cross-site-enabled (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this Mixpanel instance's config) + */ +MixpanelLib.prototype.opt_in_tracking = function(options) { + options = _.extend({ + 'enable_persistence': true + }, options); + + this._gdpr_call_func(optIn, options); + this._gdpr_update_persistence(options); +}; + +/** + * Opt the user out of data tracking and cookies/localstorage for this Mixpanel instance + * + * ### Usage + * + * // opt user out + * mixpanel.opt_out_tracking(); + * + * // opt user out with different cookie configuration from Mixpanel instance + * mixpanel.opt_out_tracking({ + * cookie_expiration: 30, + * secure_cookie: true + * }); + * + * @param {Object} [options] A dictionary of config options to override + * @param {boolean} [options.delete_user=true] If true, will delete the currently identified user's profile and clear all charges after opting the user out + * @param {boolean} [options.clear_persistence=true] If true, will delete all data stored by the sdk in persistence + * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable + * @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this Mixpanel instance's config) + * @param {string} [options.cookie_domain] Custom cookie domain (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.cross_site_cookie] Whether the opt-in cookie is set as cross-site-enabled (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this Mixpanel instance's config) + */ +MixpanelLib.prototype.opt_out_tracking = function(options) { + options = _.extend({ + 'clear_persistence': true, + 'delete_user': true + }, options); + + // delete user and clear charges since these methods may be disabled by opt-out + if (options['delete_user'] && this['people'] && this['people']._identify_called()) { + this['people'].delete_user(); + this['people'].clear_charges(); + } + + this._gdpr_call_func(optOut, options); + this._gdpr_update_persistence(options); +}; + +/** + * Check whether the user has opted in to data tracking and cookies/localstorage for this Mixpanel instance + * + * ### Usage + * + * var has_opted_in = mixpanel.has_opted_in_tracking(); + * // use has_opted_in value + * + * @param {Object} [options] A dictionary of config options to override + * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable + * @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name + * @returns {boolean} current opt-in status + */ +MixpanelLib.prototype.has_opted_in_tracking = function(options) { + return this._gdpr_call_func(hasOptedIn, options); +}; + +/** + * Check whether the user has opted out of data tracking and cookies/localstorage for this Mixpanel instance + * + * ### Usage + * + * var has_opted_out = mixpanel.has_opted_out_tracking(); + * // use has_opted_out value + * + * @param {Object} [options] A dictionary of config options to override + * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable + * @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name + * @returns {boolean} current opt-out status + */ +MixpanelLib.prototype.has_opted_out_tracking = function(options) { + return this._gdpr_call_func(hasOptedOut, options); +}; + +/** + * Clear the user's opt in/out status of data tracking and cookies/localstorage for this Mixpanel instance + * + * ### Usage + * + * // clear user's opt-in/out status + * mixpanel.clear_opt_in_out_tracking(); + * + * // clear user's opt-in/out status with specific cookie configuration - should match + * // configuration used when opt_in_tracking/opt_out_tracking methods were called. + * mixpanel.clear_opt_in_out_tracking({ + * cookie_expiration: 30, + * secure_cookie: true + * }); + * + * @param {Object} [options] A dictionary of config options to override + * @param {boolean} [options.enable_persistence=true] If true, will re-enable sdk persistence + * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable + * @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this Mixpanel instance's config) + * @param {string} [options.cookie_domain] Custom cookie domain (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.cross_site_cookie] Whether the opt-in cookie is set as cross-site-enabled (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this Mixpanel instance's config) + * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this Mixpanel instance's config) + */ +MixpanelLib.prototype.clear_opt_in_out_tracking = function(options) { + options = _.extend({ + 'enable_persistence': true + }, options); + + this._gdpr_call_func(clearOptInOut, options); + this._gdpr_update_persistence(options); +}; + +MixpanelLib.prototype.report_error = function(msg, err) { + console$1.error.apply(console$1.error, arguments); + try { + if (!err && !(msg instanceof Error)) { + msg = new Error(msg); + } + this.get_config('error_reporter')(msg, err); + } catch(err) { + console$1.error(err); + } +}; + +// EXPORTS (for closure compiler) + +// MixpanelLib Exports +MixpanelLib.prototype['init'] = MixpanelLib.prototype.init; +MixpanelLib.prototype['reset'] = MixpanelLib.prototype.reset; +MixpanelLib.prototype['disable'] = MixpanelLib.prototype.disable; +MixpanelLib.prototype['time_event'] = MixpanelLib.prototype.time_event; +MixpanelLib.prototype['track'] = MixpanelLib.prototype.track; +MixpanelLib.prototype['track_links'] = MixpanelLib.prototype.track_links; +MixpanelLib.prototype['track_forms'] = MixpanelLib.prototype.track_forms; +MixpanelLib.prototype['track_pageview'] = MixpanelLib.prototype.track_pageview; +MixpanelLib.prototype['register'] = MixpanelLib.prototype.register; +MixpanelLib.prototype['register_once'] = MixpanelLib.prototype.register_once; +MixpanelLib.prototype['unregister'] = MixpanelLib.prototype.unregister; +MixpanelLib.prototype['identify'] = MixpanelLib.prototype.identify; +MixpanelLib.prototype['alias'] = MixpanelLib.prototype.alias; +MixpanelLib.prototype['name_tag'] = MixpanelLib.prototype.name_tag; +MixpanelLib.prototype['set_config'] = MixpanelLib.prototype.set_config; +MixpanelLib.prototype['get_config'] = MixpanelLib.prototype.get_config; +MixpanelLib.prototype['get_property'] = MixpanelLib.prototype.get_property; +MixpanelLib.prototype['get_distinct_id'] = MixpanelLib.prototype.get_distinct_id; +MixpanelLib.prototype['toString'] = MixpanelLib.prototype.toString; +MixpanelLib.prototype['opt_out_tracking'] = MixpanelLib.prototype.opt_out_tracking; +MixpanelLib.prototype['opt_in_tracking'] = MixpanelLib.prototype.opt_in_tracking; +MixpanelLib.prototype['has_opted_out_tracking'] = MixpanelLib.prototype.has_opted_out_tracking; +MixpanelLib.prototype['has_opted_in_tracking'] = MixpanelLib.prototype.has_opted_in_tracking; +MixpanelLib.prototype['clear_opt_in_out_tracking'] = MixpanelLib.prototype.clear_opt_in_out_tracking; +MixpanelLib.prototype['get_group'] = MixpanelLib.prototype.get_group; +MixpanelLib.prototype['set_group'] = MixpanelLib.prototype.set_group; +MixpanelLib.prototype['add_group'] = MixpanelLib.prototype.add_group; +MixpanelLib.prototype['remove_group'] = MixpanelLib.prototype.remove_group; +MixpanelLib.prototype['track_with_groups'] = MixpanelLib.prototype.track_with_groups; +MixpanelLib.prototype['start_batch_senders'] = MixpanelLib.prototype.start_batch_senders; +MixpanelLib.prototype['stop_batch_senders'] = MixpanelLib.prototype.stop_batch_senders; + +// MixpanelPersistence Exports +MixpanelPersistence.prototype['properties'] = MixpanelPersistence.prototype.properties; +MixpanelPersistence.prototype['update_search_keyword'] = MixpanelPersistence.prototype.update_search_keyword; +MixpanelPersistence.prototype['update_referrer_info'] = MixpanelPersistence.prototype.update_referrer_info; +MixpanelPersistence.prototype['get_cross_subdomain'] = MixpanelPersistence.prototype.get_cross_subdomain; +MixpanelPersistence.prototype['clear'] = MixpanelPersistence.prototype.clear; + + +var instances = {}; +var extend_mp = function() { + // add all the sub mixpanel instances + _.each(instances, function(instance, name) { + if (name !== PRIMARY_INSTANCE_NAME) { mixpanel_master[name] = instance; } + }); + + // add private functions as _ + mixpanel_master['_'] = _; +}; + +var override_mp_init_func = function() { + // we override the snippets init function to handle the case where a + // user initializes the mixpanel library after the script loads & runs + mixpanel_master['init'] = function(token, config, name) { + if (name) { + // initialize a sub library + if (!mixpanel_master[name]) { + mixpanel_master[name] = instances[name] = create_mplib(token, config, name); + mixpanel_master[name]._loaded(); + } + return mixpanel_master[name]; + } else { + var instance = mixpanel_master; + + if (instances[PRIMARY_INSTANCE_NAME]) { + // main mixpanel lib already initialized + instance = instances[PRIMARY_INSTANCE_NAME]; + } else if (token) { + // intialize the main mixpanel lib + instance = create_mplib(token, config, PRIMARY_INSTANCE_NAME); + instance._loaded(); + instances[PRIMARY_INSTANCE_NAME] = instance; + } + + mixpanel_master = instance; + if (init_type === INIT_SNIPPET) { + window$1[PRIMARY_INSTANCE_NAME] = mixpanel_master; + } + extend_mp(); + } + }; +}; + +var add_dom_loaded_handler = function() { + // Cross browser DOM Loaded support + function dom_loaded_handler() { + // function flag since we only want to execute this once + if (dom_loaded_handler.done) { return; } + dom_loaded_handler.done = true; + + DOM_LOADED = true; + ENQUEUE_REQUESTS = false; + + _.each(instances, function(inst) { + inst._dom_loaded(); + }); + } + + function do_scroll_check() { + try { + document$1.documentElement.doScroll('left'); + } catch(e) { + setTimeout(do_scroll_check, 1); + return; + } + + dom_loaded_handler(); + } + + if (document$1.addEventListener) { + if (document$1.readyState === 'complete') { + // safari 4 can fire the DOMContentLoaded event before loading all + // external JS (including this file). you will see some copypasta + // on the internet that checks for 'complete' and 'loaded', but + // 'loaded' is an IE thing + dom_loaded_handler(); + } else { + document$1.addEventListener('DOMContentLoaded', dom_loaded_handler, false); + } + } else if (document$1.attachEvent) { + // IE + document$1.attachEvent('onreadystatechange', dom_loaded_handler); + + // check to make sure we arn't in a frame + var toplevel = false; + try { + toplevel = window$1.frameElement === null; + } catch(e) { + // noop + } + + if (document$1.documentElement.doScroll && toplevel) { + do_scroll_check(); + } + } + + // fallback handler, always will work + _.register_event(window$1, 'load', dom_loaded_handler, true); +}; + +function init_as_module() { + init_type = INIT_MODULE; + mixpanel_master = new MixpanelLib(); + + override_mp_init_func(); + mixpanel_master['init'](); + add_dom_loaded_handler(); + + return mixpanel_master; +} + +var mixpanel = init_as_module(); + +var mixpanel_cjs = mixpanel; + +var mixpanel$1 = /*#__PURE__*/_mergeNamespaces({ + __proto__: null, + default: mixpanel_cjs +}, [mixpanel_cjs]); + +const VERSION$1 = packageInfo.version; +// Needed to avoid error in CJS builds on some bundlers. +const mixpanelLib = mixpanel_cjs || mixpanel$1; +let mixpanelInstance; +/** + * Enum of mixpanel events + * @hidden + */ +const MIXPANEL_EVENT = { + VISUAL_SDK_RENDER_START: 'visual-sdk-render-start', + VISUAL_SDK_CALLED_INIT: 'visual-sdk-called-init', + VISUAL_SDK_RENDER_COMPLETE: 'visual-sdk-render-complete', + VISUAL_SDK_RENDER_FAILED: 'visual-sdk-render-failed', + VISUAL_SDK_TRIGGER: 'visual-sdk-trigger', + VISUAL_SDK_ON: 'visual-sdk-on', + VISUAL_SDK_IFRAME_LOAD_PERFORMANCE: 'visual-sdk-iframe-load-performance', + VISUAL_SDK_EMBED_CREATE: 'visual-sdk-embed-create', + VERCEL_INTEGRATION_COMPLETED: 'vercel-integration-completed', +}; +let isMixpanelInitialized = false; +let eventQueue = []; +/** + * Pushes the event with its Property key-value map to mixpanel. + * @param eventId + * @param eventProps + */ +function uploadMixpanelEvent(eventId, eventProps = {}) { + if (!isMixpanelInitialized) { + eventQueue.push({ eventId, eventProps }); + return; + } + mixpanelInstance.track(eventId, eventProps); +} +/** + * + */ +function emptyQueue() { + if (!isMixpanelInitialized) { + return; + } + eventQueue.forEach((event) => { + uploadMixpanelEvent(event.eventId, event.eventProps); + }); + eventQueue = []; +} +/** + * + * @param sessionInfo + */ +function initMixpanel(sessionInfo) { + if (!sessionInfo || !sessionInfo.mixpanelToken) { + logger$3.error(ERROR_MESSAGE.MIXPANEL_TOKEN_NOT_FOUND); + return; + } + // On a public cluster the user is anonymous, so don't set the identify to + // userGUID + const isPublicCluster = !!sessionInfo.isPublicUser; + const token = sessionInfo.mixpanelToken; + try { + if (token) { + mixpanelInstance = mixpanelLib.init(token, undefined, 'tsEmbed'); + if (!isPublicCluster) { + mixpanelInstance.identify(sessionInfo.userGUID); + } + mixpanelInstance.register_once({ + clusterId: sessionInfo.clusterId, + clusterName: sessionInfo.clusterName, + releaseVersion: sessionInfo.releaseVersion, + hostAppUrl: window?.location?.host || '', + sdkVersion: VERSION$1, + }); + isMixpanelInitialized = true; + emptyQueue(); + } + } + catch (e) { + logger$3.error('Error initializing mixpanel', e); + } +} + +var eventemitter3 = createCommonjsModule(function (module) { + +var has = Object.prototype.hasOwnProperty + , prefix = '~'; + +/** + * Constructor to create a storage for our `EE` objects. + * An `Events` instance is a plain object whose properties are event names. + * + * @constructor + * @private + */ +function Events() {} + +// +// We try to not inherit from `Object.prototype`. In some engines creating an +// instance in this way is faster than calling `Object.create(null)` directly. +// If `Object.create(null)` is not supported we prefix the event names with a +// character to make sure that the built-in object properties are not +// overridden or used as an attack vector. +// +if (Object.create) { + Events.prototype = Object.create(null); + + // + // This hack is needed because the `__proto__` property is still inherited in + // some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5. + // + if (!new Events().__proto__) prefix = false; +} + +/** + * Representation of a single event listener. + * + * @param {Function} fn The listener function. + * @param {*} context The context to invoke the listener with. + * @param {Boolean} [once=false] Specify if the listener is a one-time listener. + * @constructor + * @private + */ +function EE(fn, context, once) { + this.fn = fn; + this.context = context; + this.once = once || false; +} + +/** + * Add a listener for a given event. + * + * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. + * @param {(String|Symbol)} event The event name. + * @param {Function} fn The listener function. + * @param {*} context The context to invoke the listener with. + * @param {Boolean} once Specify if the listener is a one-time listener. + * @returns {EventEmitter} + * @private + */ +function addListener(emitter, event, fn, context, once) { + if (typeof fn !== 'function') { + throw new TypeError('The listener must be a function'); + } + + var listener = new EE(fn, context || emitter, once) + , evt = prefix ? prefix + event : event; + + if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++; + else if (!emitter._events[evt].fn) emitter._events[evt].push(listener); + else emitter._events[evt] = [emitter._events[evt], listener]; + + return emitter; +} + +/** + * Clear event by name. + * + * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. + * @param {(String|Symbol)} evt The Event name. + * @private + */ +function clearEvent(emitter, evt) { + if (--emitter._eventsCount === 0) emitter._events = new Events(); + else delete emitter._events[evt]; +} + +/** + * Minimal `EventEmitter` interface that is molded against the Node.js + * `EventEmitter` interface. + * + * @constructor + * @public + */ +function EventEmitter() { + this._events = new Events(); + this._eventsCount = 0; +} + +/** + * Return an array listing the events for which the emitter has registered + * listeners. + * + * @returns {Array} + * @public + */ +EventEmitter.prototype.eventNames = function eventNames() { + var names = [] + , events + , name; + + if (this._eventsCount === 0) return names; + + for (name in (events = this._events)) { + if (has.call(events, name)) names.push(prefix ? name.slice(1) : name); + } + + if (Object.getOwnPropertySymbols) { + return names.concat(Object.getOwnPropertySymbols(events)); + } + + return names; +}; + +/** + * Return the listeners registered for a given event. + * + * @param {(String|Symbol)} event The event name. + * @returns {Array} The registered listeners. + * @public + */ +EventEmitter.prototype.listeners = function listeners(event) { + var evt = prefix ? prefix + event : event + , handlers = this._events[evt]; + + if (!handlers) return []; + if (handlers.fn) return [handlers.fn]; + + for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) { + ee[i] = handlers[i].fn; + } + + return ee; +}; + +/** + * Return the number of listeners listening to a given event. + * + * @param {(String|Symbol)} event The event name. + * @returns {Number} The number of listeners. + * @public + */ +EventEmitter.prototype.listenerCount = function listenerCount(event) { + var evt = prefix ? prefix + event : event + , listeners = this._events[evt]; + + if (!listeners) return 0; + if (listeners.fn) return 1; + return listeners.length; +}; + +/** + * Calls each of the listeners registered for a given event. + * + * @param {(String|Symbol)} event The event name. + * @returns {Boolean} `true` if the event had listeners, else `false`. + * @public + */ +EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { + var evt = prefix ? prefix + event : event; + + if (!this._events[evt]) return false; + + var listeners = this._events[evt] + , len = arguments.length + , args + , i; + + if (listeners.fn) { + if (listeners.once) this.removeListener(event, listeners.fn, undefined, true); + + switch (len) { + case 1: return listeners.fn.call(listeners.context), true; + case 2: return listeners.fn.call(listeners.context, a1), true; + case 3: return listeners.fn.call(listeners.context, a1, a2), true; + case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true; + case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true; + case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true; + } + + for (i = 1, args = new Array(len -1); i < len; i++) { + args[i - 1] = arguments[i]; + } + + listeners.fn.apply(listeners.context, args); + } else { + var length = listeners.length + , j; + + for (i = 0; i < length; i++) { + if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true); + + switch (len) { + case 1: listeners[i].fn.call(listeners[i].context); break; + case 2: listeners[i].fn.call(listeners[i].context, a1); break; + case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break; + case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break; + default: + if (!args) for (j = 1, args = new Array(len -1); j < len; j++) { + args[j - 1] = arguments[j]; + } + + listeners[i].fn.apply(listeners[i].context, args); + } + } + } + + return true; +}; + +/** + * Add a listener for a given event. + * + * @param {(String|Symbol)} event The event name. + * @param {Function} fn The listener function. + * @param {*} [context=this] The context to invoke the listener with. + * @returns {EventEmitter} `this`. + * @public + */ +EventEmitter.prototype.on = function on(event, fn, context) { + return addListener(this, event, fn, context, false); +}; + +/** + * Add a one-time listener for a given event. + * + * @param {(String|Symbol)} event The event name. + * @param {Function} fn The listener function. + * @param {*} [context=this] The context to invoke the listener with. + * @returns {EventEmitter} `this`. + * @public + */ +EventEmitter.prototype.once = function once(event, fn, context) { + return addListener(this, event, fn, context, true); +}; + +/** + * Remove the listeners of a given event. + * + * @param {(String|Symbol)} event The event name. + * @param {Function} fn Only remove the listeners that match this function. + * @param {*} context Only remove the listeners that have this context. + * @param {Boolean} once Only remove one-time listeners. + * @returns {EventEmitter} `this`. + * @public + */ +EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) { + var evt = prefix ? prefix + event : event; + + if (!this._events[evt]) return this; + if (!fn) { + clearEvent(this, evt); + return this; + } + + var listeners = this._events[evt]; + + if (listeners.fn) { + if ( + listeners.fn === fn && + (!once || listeners.once) && + (!context || listeners.context === context) + ) { + clearEvent(this, evt); + } + } else { + for (var i = 0, events = [], length = listeners.length; i < length; i++) { + if ( + listeners[i].fn !== fn || + (once && !listeners[i].once) || + (context && listeners[i].context !== context) + ) { + events.push(listeners[i]); + } + } + + // + // Reset the array, or remove it completely if we have no more listeners. + // + if (events.length) this._events[evt] = events.length === 1 ? events[0] : events; + else clearEvent(this, evt); + } + + return this; +}; + +/** + * Remove all listeners, or those of the specified event. + * + * @param {(String|Symbol)} [event] The event name. + * @returns {EventEmitter} `this`. + * @public + */ +EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) { + var evt; + + if (event) { + evt = prefix ? prefix + event : event; + if (this._events[evt]) clearEvent(this, evt); + } else { + this._events = new Events(); + this._eventsCount = 0; + } + + return this; +}; + +// +// Alias methods names because people roll like that. +// +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; +EventEmitter.prototype.addListener = EventEmitter.prototype.on; + +// +// Expose the prefix. +// +EventEmitter.prefixed = prefix; + +// +// Allow `EventEmitter` to be imported as module namespace. +// +EventEmitter.EventEmitter = EventEmitter; + +// +// Expose the module. +// +{ + module.exports = EventEmitter; +} +}); + +var ReportType; +(function (ReportType) { + ReportType["CSP_VIOLATION"] = "csp-violation"; + ReportType["DEPRECATION"] = "deprecation"; + ReportType["INTERVENTION"] = "intervention"; +})(ReportType || (ReportType = {})); +let globalObserver = null; +/** + * Register a global ReportingObserver to capture all unhandled errors + * @param overrideExisting boolean to override existing observer + * @returns ReportingObserver | null + */ +function registerReportingObserver(overrideExisting = false) { + if (!(window.ReportingObserver)) { + logger$3.warn(ERROR_MESSAGE.MISSING_REPORTING_OBSERVER); + return null; + } + if (overrideExisting) { + resetGlobalReportingObserver(); + } + if (globalObserver) { + return globalObserver; + } + const embedConfig = getEmbedConfig(); + globalObserver = new ReportingObserver((reports) => { + reports.forEach((report) => { + const { type, url, body } = report; + const reportBody = body; + const isThoughtSpotHost = url + && url.startsWith(embedConfig.thoughtSpotHost); + const isFrameHostError = type === ReportType.CSP_VIOLATION + && reportBody.effectiveDirective === 'frame-ancestors'; + if (isThoughtSpotHost && isFrameHostError) { + if (!embedConfig.suppressErrorAlerts) { + alert(ERROR_MESSAGE.CSP_VIOLATION_ALERT); + } + logger$3.error(ERROR_MESSAGE.CSP_FRAME_HOST_VIOLATION_LOG_MESSAGE); + } + }); + }, { buffered: true }); + globalObserver.observe(); + return globalObserver; +} +/** + * Resets the global ReportingObserver + */ +function resetGlobalReportingObserver() { + if (globalObserver) + globalObserver.disconnect(); + globalObserver = null; +} + +/** + * + * @param url + * @param options + */ +function tokenizedFailureLoggedFetch(url, options = {}) { + return tokenizedFetch(url, options).then(async (r) => { + if (!r.ok && r.type !== 'opaqueredirect' && r.type !== 'opaque') { + logger$3.error(`Failed to fetch ${url}`, await r.text?.()); + } + return r; + }); +} +/** + * Fetches the session info from the ThoughtSpot server. + * @param thoughtspotHost + * @returns {Promise} + * @example + * ```js + * const response = await sessionInfoService(); + * ``` + */ +async function fetchPreauthInfoService(thoughtspotHost) { + const sessionInfoPath = `${thoughtspotHost}${EndPoints.PREAUTH_INFO}`; + const handleError = (e) => { + const error = new Error(`Failed to fetch auth info: ${e.message || e.statusText}`); + error.status = e.status; // Attach the status code to the error object + throw error; + }; + try { + const response = await tokenizedFailureLoggedFetch(sessionInfoPath); + return response; + } + catch (e) { + handleError(e); + return null; + } +} +/** + * Fetches the session info from the ThoughtSpot server. + * @param thoughtspotHost + * @returns {Promise} + * @example + * ```js + * const response = await sessionInfoService(); + * ``` + */ +async function fetchSessionInfoService(thoughtspotHost) { + const sessionInfoPath = `${thoughtspotHost}${EndPoints.SESSION_INFO}`; + const response = await tokenizedFailureLoggedFetch(sessionInfoPath); + if (!response.ok) { + throw new Error(`Failed to fetch session info: ${response.statusText}`); + } + const data = await response.json(); + return data; +} +/** + * + * @param thoughtSpotHost + */ +async function fetchLogoutService(thoughtSpotHost) { + return tokenizedFailureLoggedFetch(`${thoughtSpotHost}${EndPoints.LOGOUT}`, { + credentials: 'include', + method: 'POST', + headers: { + 'x-requested-by': 'ThoughtSpot', + }, + }); +} +/** + * Is active service to check if the user is logged in. + * @param thoughtSpotHost + * @version SDK: 1.28.4 | ThoughtSpot: * + */ +async function isActiveService(thoughtSpotHost) { + const isActiveUrl = `${thoughtSpotHost}${EndPoints.IS_ACTIVE}`; + try { + const res = await tokenizedFetch(isActiveUrl, { + credentials: 'include', + }); + return res.ok; + } + catch (e) { + logger$3.warn(`Is Logged In Service failed : ${e.message}`); + } + return false; +} + +let sessionInfo = null; +let preauthInfo = null; +/** + * Processes the session info response and returns the session info object. + * @param preauthInfoResp {any} Response from the session info API. + * @returns {PreauthInfo} The session info object. + * @example ```js + * const preauthInfoResp = await fetch(sessionInfoPath); + * const sessionInfo = await formatPreauthInfo(preauthInfoResp); + * console.log(sessionInfo); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + */ +const formatPreauthInfo = async (preauthInfoResp) => { + try { + // Convert Headers to a plain object + const headers = {}; + preauthInfoResp?.headers?.forEach((value, key) => { + headers[key] = value; + }); + const data = await preauthInfoResp.json(); + return { + ...data, + status: 200, + headers, + }; + } + catch (error) { + return null; + } +}; +/** + * Returns the session info object and caches it for future use. + * Once fetched the session info object is cached and returned from the cache on + * subsequent calls. + * @example ```js + * const preauthInfo = await getPreauthInfo(); + * console.log(preauthInfo); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + * @returns {Promise} The session info object. + */ +async function getPreauthInfo(allowCache = true) { + if (!allowCache || !preauthInfo) { + try { + const host = getEmbedConfig().thoughtSpotHost; + const sessionResponse = await fetchPreauthInfoService(host); + const processedPreauthInfo = await formatPreauthInfo(sessionResponse); + preauthInfo = processedPreauthInfo; + } + catch (error) { + return null; + } + } + return preauthInfo; +} +/** + * Returns the cached session info object and caches it for future use. + * Once fetched the session info object is cached and returned from the cache on + * subsequent calls. + * This cache is cleared when inti is called OR resetCachedSessionInfo is called. + * @example ```js + * const sessionInfo = await getSessionInfo(); + * console.log(sessionInfo); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + * @returns {Promise} The session info object. + */ +async function getSessionInfo() { + if (!sessionInfo) { + const host = getEmbedConfig().thoughtSpotHost; + const sessionResponse = await fetchSessionInfoService(host); + const processedSessionInfo = getSessionDetails(sessionResponse); + sessionInfo = processedSessionInfo; + } + return sessionInfo; +} +/** + * Processes the session info response and returns the session info object. + * @param sessionInfoResp {any} Response from the session info API. + * @returns {SessionInfo} The session info object. + * @example ```js + * const sessionInfoResp = await fetch(sessionInfoPath); + * const sessionInfo = getSessionDetails(sessionInfoResp); + * console.log(sessionInfo); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + */ +const getSessionDetails = (sessionInfoResp) => { + const devMixpanelToken = sessionInfoResp.configInfo.mixpanelConfig.devSdkKey; + const prodMixpanelToken = sessionInfoResp.configInfo.mixpanelConfig.prodSdkKey; + const mixpanelToken = sessionInfoResp.configInfo.mixpanelConfig.production + ? prodMixpanelToken + : devMixpanelToken; + return { + userGUID: sessionInfoResp.userGUID, + mixpanelToken, + isPublicUser: sessionInfoResp.configInfo.isPublicUser, + releaseVersion: sessionInfoResp.releaseVersion, + clusterId: sessionInfoResp.configInfo.selfClusterId, + clusterName: sessionInfoResp.configInfo.selfClusterName, + ...sessionInfoResp, + }; +}; +/** + * Resets the cached session info object and forces a new fetch on the next call. + * @example ```js + * resetCachedSessionInfo(); + * const sessionInfo = await getSessionInfo(); + * console.log(sessionInfo); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + * @returns {void} + */ +function resetCachedSessionInfo() { + sessionInfo = null; +} +/** + * Resets the cached preauth info object and forces a new fetch on the next call. + * @example ```js + * resetCachedPreauthInfo(); + * const preauthInfo = await getPreauthInfo(); + * console.log(preauthInfo); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + * @returns {void} + */ +function resetCachedPreauthInfo() { + preauthInfo = null; +} + +/** + * This function resets all the services that are cached in the SDK. + * This is to be called when the user logs out of the application and also + * when init is called again. + * @version SDK: 1.30.2 | ThoughtSpot: * + */ +function resetAllCachedServices() { + resetCachedAuthToken(); + resetCachedSessionInfo(); + resetCachedPreauthInfo(); +} + +let loggedInStatus = false; +let samlAuthWindow = null; +let samlCompletionPromise = null; +let releaseVersion = ''; +const SSO_REDIRECTION_MARKER_GUID = '5e16222e-ef02-43e9-9fbd-24226bf3ce5b'; +/** + * Enum for auth failure types. + * This value is passed to the listener for {@link AuthStatus.FAILURE}. + * @group Authentication / Init + */ +var AuthFailureType; +(function (AuthFailureType) { + /** + * Authentication failed in the SDK authentication flow. + * + * Emitted when `init()` or auto-authentication cannot establish a logged-in session. + * For example, this can happen because of an invalid token, an auth request failure, + * or an auth promise rejection. + */ + AuthFailureType["SDK"] = "SDK"; + /** + * Browser cookie access is blocked for the embedded app. + * + * Emitted when the iframe reports that required cookies + * cannot be read or sent, commonly due to third-party cookie restrictions. + */ + AuthFailureType["NO_COOKIE_ACCESS"] = "NO_COOKIE_ACCESS"; + /** + * The current authentication token or session has expired. + * + * Emitted when the embed receives an auth-expiry signal and starts auth refresh + * handling. + */ + AuthFailureType["EXPIRY"] = "EXPIRY"; + /** + * A generic authentication failure that does not match a more specific type. + * + * Emitted as a fallback for app-reported auth failures in standard auth flows. + */ + AuthFailureType["OTHER"] = "OTHER"; + /** + * The user session timed out due to inactivity. + * + * Emitted when the app reports an idle-session timeout. + */ + AuthFailureType["IDLE_SESSION_TIMEOUT"] = "IDLE_SESSION_TIMEOUT"; + /** + * The app reports that the user is unauthenticated. + * + * Used primarily to classify unauthenticated failures in Embedded SSO flows. + */ + AuthFailureType["UNAUTHENTICATED_FAILURE"] = "UNAUTHENTICATED_FAILURE"; +})(AuthFailureType || (AuthFailureType = {})); +/** + * Enum for auth status emitted by the emitter returned from {@link init}. + * @group Authentication / Init + */ +var AuthStatus; +(function (AuthStatus) { + /** + * Emits when the SDK fails to authenticate. + */ + AuthStatus["FAILURE"] = "FAILURE"; + /** + * Emits when the SDK authentication step completes + * successfully (e.g., token exchange, cookie set). + * This fires before any iframe is rendered. Use + * this to know that auth passed and it is safe to + * proceed with rendering. The callback receives no + * arguments. + * @example + * ```js + * const authEE = init({ ... }); + * authEE.on(AuthStatus.SDK_SUCCESS, () => { + * // Auth done, iframe not loaded yet + * }); + * ``` + */ + AuthStatus["SDK_SUCCESS"] = "SDK_SUCCESS"; + /** + * @hidden + * Emits when iframe is loaded and session + * information is available. + */ + AuthStatus["SESSION_INFO_SUCCESS"] = "SESSION_INFO_SUCCESS"; + /** + * Emits when the ThoughtSpot app inside the + * embedded iframe confirms its session is active. + * This fires after the iframe loads and sends back an `AuthInit` event. + * @param sessionInfo Information about the user session, with details like `userGUID`. + * @see EmbedEvent.AuthInit + * @example + * ```js + * const authEE = init({ ... }); + * authEE.on(AuthStatus.SUCCESS, (sessionInfo) => { + * // App is loaded and authenticated + * console.log(sessionInfo.userGUID); + * }); + * ``` + */ + AuthStatus["SUCCESS"] = "SUCCESS"; + /** + * Emits when a user logs out + */ + AuthStatus["LOGOUT"] = "LOGOUT"; + /** + * Emitted when inPopup is true in the SAMLRedirect flow and the + * popup is waiting to be triggered either programmatically + * or by the trigger button. + * @version SDK: 1.19.0 + */ + AuthStatus["WAITING_FOR_POPUP"] = "WAITING_FOR_POPUP"; + /** + * Emitted when the SAML popup is closed without authentication + */ + AuthStatus["SAML_POPUP_CLOSED_NO_AUTH"] = "SAML_POPUP_CLOSED_NO_AUTH"; +})(AuthStatus || (AuthStatus = {})); +/** + * Events which can be triggered on the emitter returned from {@link init}. + * @group Authentication / Init + */ +var AuthEvent; +(function (AuthEvent) { + /** + * Manually trigger the SSO popup. This is useful when + * authStatus is SAMLRedirect/OIDCRedirect and inPopup is set to true + */ + AuthEvent["TRIGGER_SSO_POPUP"] = "TRIGGER_SSO_POPUP"; +})(AuthEvent || (AuthEvent = {})); +let authEE; +/** + * + * @param eventEmitter + */ +function setAuthEE(eventEmitter) { + authEE = eventEmitter; +} +/** + * + */ +function notifyAuthSDKSuccess() { + if (!authEE) { + logger$3.error(ERROR_MESSAGE.SDK_NOT_INITIALIZED); + return; + } + authEE.emit(AuthStatus.SDK_SUCCESS); +} +/** + * + */ +async function notifyAuthSuccess() { + if (!authEE) { + logger$3.error(ERROR_MESSAGE.SDK_NOT_INITIALIZED); + return; + } + try { + getPreauthInfo(); + const sessionInfo = await getSessionInfo(); + authEE.emit(AuthStatus.SUCCESS, sessionInfo); + } + catch (e) { + logger$3.error(ERROR_MESSAGE.SESSION_INFO_FAILED); + } +} +/** + * + * @param failureType + */ +function notifyAuthFailure(failureType) { + if (!authEE) { + logger$3.error(ERROR_MESSAGE.SDK_NOT_INITIALIZED); + return; + } + authEE.emit(AuthStatus.FAILURE, failureType); +} +/** + * + */ +function notifyLogout() { + if (!authEE) { + logger$3.error(ERROR_MESSAGE.SDK_NOT_INITIALIZED); + return; + } + authEE.emit(AuthStatus.LOGOUT); +} +/** + * Check if we are logged into the ThoughtSpot cluster + * @param thoughtSpotHost The ThoughtSpot cluster hostname or IP + */ +async function isLoggedIn(thoughtSpotHost) { + try { + const response = await isActiveService(thoughtSpotHost); + return response; + } + catch (e) { + return false; + } +} +/** + * Services to be called after the login is successful, + * This should be called after the cookie is set for cookie auth or + * after the token is set for cookieless. + * @return {Promise} + * @example + * ```js + * await postLoginService(); + * ``` + * @version SDK: 1.28.3 | ThoughtSpot: * + */ +async function postLoginService() { + try { + getPreauthInfo(); + const sessionInfo = await getSessionInfo(); + releaseVersion = sessionInfo.releaseVersion; + const embedConfig = getEmbedConfig(); + if (!embedConfig.disableSDKTracking) { + initMixpanel(sessionInfo); + } + } + catch (e) { + logger$3.error('Post login services failed.', e.message, e); + } +} +/** + * Return releaseVersion if available + */ +function getReleaseVersion() { + return releaseVersion; +} +/** + * Check if we are stuck at the SSO redirect URL + */ +function isAtSSORedirectUrl() { + return window.location.href.indexOf(getSSOMarker(SSO_REDIRECTION_MARKER_GUID)) >= 0; +} +/** + * Remove the SSO redirect URL marker + */ +function removeSSORedirectUrlMarker() { + // Note (sunny): This will leave a # around even if it was not in the URL + // to begin with. Trying to remove the hash by changing window.location will + // reload the page which we don't want. We'll live with adding an + // unnecessary hash to the parent page URL until we find any use case where + // that creates an issue. + // Replace any occurrences of ?ssoMarker=guid or &ssoMarker=guid. + let updatedHash = window.location.hash.replace(`?${getSSOMarker(SSO_REDIRECTION_MARKER_GUID)}`, ''); + updatedHash = updatedHash.replace(`&${getSSOMarker(SSO_REDIRECTION_MARKER_GUID)}`, ''); + window.location.hash = updatedHash; +} +/** + * Perform token based authentication + * @param embedConfig The embed configuration + */ +const doTokenAuth = async (embedConfig) => { + const { thoughtSpotHost, username, authEndpoint, getAuthToken, } = embedConfig; + if (!authEndpoint && !getAuthToken) { + throw new Error('Either auth endpoint or getAuthToken function must be provided'); + } + loggedInStatus = await isLoggedIn(thoughtSpotHost); + if (!loggedInStatus) { + let authToken; + try { + authToken = await getAuthenticationToken(embedConfig); + } + catch (e) { + loggedInStatus = false; + throw e; + } + let resp; + try { + resp = await fetchAuthPostService(thoughtSpotHost, username, authToken); + } + catch (e) { + resp = await fetchAuthService(thoughtSpotHost, username, authToken); + } + // token login issues a 302 when successful + loggedInStatus = resp.ok || resp.type === 'opaqueredirect'; + if (loggedInStatus && embedConfig.detectCookieAccessSlow) { + // When 3rd party cookie access is blocked, this will fail because + // cookies will not be sent with the call. + loggedInStatus = await isLoggedIn(thoughtSpotHost); + } + } + return loggedInStatus; +}; +/** + * Validate embedConfig parameters required for cookielessTokenAuth + * @param embedConfig The embed configuration + */ +const doCookielessTokenAuth = async (embedConfig) => { + const { authEndpoint, getAuthToken } = embedConfig; + if (!authEndpoint && !getAuthToken) { + throw new Error('Either auth endpoint or getAuthToken function must be provided'); + } + let authSuccess = false; + try { + const authToken = await getAuthenticationToken(embedConfig); + if (authToken) { + authSuccess = true; + } + } + catch { + authSuccess = false; + } + return authSuccess; +}; +/** + * Perform basic authentication to the ThoughtSpot cluster using the cluster + * credentials. + * + * Warning: This feature is primarily intended for developer testing. It is + * strongly advised not to use this authentication method in production. + * @param embedConfig The embed configuration + */ +const doBasicAuth = async (embedConfig) => { + const { thoughtSpotHost, username, password } = embedConfig; + const loggedIn = await isLoggedIn(thoughtSpotHost); + if (!loggedIn) { + const response = await fetchBasicAuthService(thoughtSpotHost, username, password); + loggedInStatus = response.ok; + if (embedConfig.detectCookieAccessSlow) { + loggedInStatus = await isLoggedIn(thoughtSpotHost); + } + } + else { + loggedInStatus = true; + } + return loggedInStatus; +}; +/** + * + * @param ssoURL + * @param triggerContainer + * @param triggerText + */ +async function samlPopupFlow(ssoURL, triggerContainer, triggerText) { + let popupClosedCheck; + const openPopup = () => { + if (samlAuthWindow === null || samlAuthWindow.closed) { + samlAuthWindow = window.open(ssoURL, '_blank', 'location=no,height=570,width=520,scrollbars=yes,status=yes'); + if (samlAuthWindow) { + popupClosedCheck = setInterval(() => { + if (samlAuthWindow.closed) { + clearInterval(popupClosedCheck); + if (samlCompletionPromise && !samlCompletionResolved) { + authEE?.emit(AuthStatus.SAML_POPUP_CLOSED_NO_AUTH); + } + } + }, 500); + } + } + else { + samlAuthWindow.focus(); + } + }; + let samlCompletionResolved = false; + authEE?.emit(AuthStatus.WAITING_FOR_POPUP); + const containerEl = getDOMNode(triggerContainer); + if (containerEl) { + containerEl.innerHTML = ''; + const authElem = document.getElementById('ts-auth-btn'); + authElem.textContent = triggerText; + authElem.addEventListener('click', openPopup, { once: true }); + } + samlCompletionPromise = samlCompletionPromise || new Promise((resolve, reject) => { + window.addEventListener('message', (e) => { + if (e.data.type === EmbedEvent.SAMLComplete) { + if (e.data.accessToken) { + const decodedToken = decodeURIComponent(e.data.accessToken); + storeAuthTokenInCache(decodedToken); + } + samlCompletionResolved = true; + if (popupClosedCheck) { + clearInterval(popupClosedCheck); + } + e.source.close(); + resolve(); + } + }); + }); + authEE?.once(AuthEvent.TRIGGER_SSO_POPUP, openPopup); + return samlCompletionPromise; +} +/** + * Perform SAML authentication + * @param embedConfig The embed configuration + * @param ssoEndPoint + */ +const doSSOAuth = async (embedConfig, ssoEndPoint) => { + const { thoughtSpotHost } = embedConfig; + const loggedIn = await isLoggedIn(thoughtSpotHost); + if (loggedIn) { + if (isAtSSORedirectUrl()) { + removeSSORedirectUrlMarker(); + } + loggedInStatus = true; + return; + } + // we have already tried authentication and it did not succeed, restore + // the current URL to the original one and invoke the callback. + if (isAtSSORedirectUrl()) { + removeSSORedirectUrlMarker(); + loggedInStatus = false; + return; + } + const ssoURL = `${thoughtSpotHost}${ssoEndPoint}`; + if (embedConfig.inPopup) { + await samlPopupFlow(ssoURL, embedConfig.authTriggerContainer, embedConfig.authTriggerText); + const cachedToken = getCacheAuthToken(); + if (cachedToken) { + loggedInStatus = true; + } + else { + loggedInStatus = await isLoggedIn(thoughtSpotHost); + } + return; + } + window.location.href = ssoURL; +}; +const doSamlAuth = async (embedConfig) => { + const { thoughtSpotHost } = embedConfig; + // redirect for SSO, when the SSO authentication is done, this page will be + // loaded again and the same JS will execute again. + const ssoRedirectUrl = embedConfig.inPopup + ? `${thoughtSpotHost}/v2/#/embed/saml-complete` + : getRedirectUrl(window.location.href, SSO_REDIRECTION_MARKER_GUID, embedConfig.redirectPath); + // bring back the page to the same URL + const ssoEndPoint = `${EndPoints.SAML_LOGIN_TEMPLATE(encodeURIComponent(ssoRedirectUrl))}`; + await doSSOAuth(embedConfig, ssoEndPoint); + return loggedInStatus; +}; +const doOIDCAuth = async (embedConfig) => { + const { thoughtSpotHost } = embedConfig; + // redirect for SSO, when the SSO authentication is done, this page will be + // loaded again and the same JS will execute again. + const ssoRedirectUrl = embedConfig.noRedirect || embedConfig.inPopup + ? `${thoughtSpotHost}/v2/#/embed/saml-complete` + : getRedirectUrl(window.location.href, SSO_REDIRECTION_MARKER_GUID, embedConfig.redirectPath); + // bring back the page to the same URL + const baseEndpoint = `${EndPoints.OIDC_LOGIN_TEMPLATE(encodeURIComponent(ssoRedirectUrl))}`; + const ssoEndPoint = `${baseEndpoint}${baseEndpoint.includes('?') ? '&' : '?'}forceSAMLAutoRedirect=true`; + await doSSOAuth(embedConfig, ssoEndPoint); + return loggedInStatus; +}; +const logout$1 = async (embedConfig) => { + const { thoughtSpotHost } = embedConfig; + await fetchLogoutService(thoughtSpotHost); + resetAllCachedServices(); + const thoughtspotIframes = document.querySelectorAll("[data-ts-iframe='true']"); + if (thoughtspotIframes?.length) { + thoughtspotIframes.forEach((el) => { + el.parentElement.innerHTML = embedConfig.loginFailedMessage; + }); + } + loggedInStatus = false; + return loggedInStatus; +}; +/** + * Perform authentication on the ThoughtSpot cluster + * @param embedConfig The embed configuration + */ +const authenticate = async (embedConfig) => { + const { authType } = embedConfig; + switch (authType) { + case AuthType.SSO: + case AuthType.SAMLRedirect: + case AuthType.SAML: + return doSamlAuth(embedConfig); + case AuthType.OIDC: + case AuthType.OIDCRedirect: + return doOIDCAuth(embedConfig); + case AuthType.AuthServer: + case AuthType.TrustedAuthToken: + return doTokenAuth(embedConfig); + case AuthType.TrustedAuthTokenCookieless: + return doCookielessTokenAuth(embedConfig); + case AuthType.Basic: + return doBasicAuth(embedConfig); + default: + return Promise.resolve(true); + } +}; + +if (typeof Promise.withResolvers === 'undefined') { + Promise.withResolvers = () => { + let resolve; + let reject; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; + }; +} + +/** + * Reloads the ThoughtSpot iframe. + * @param iFrame + */ +const reload = (iFrame) => { + const src = iFrame.src; + iFrame.src = ''; + setTimeout(() => { + iFrame.src = src; + }, 100); +}; +/** + * Post iframe message. + * @param iFrame + * @param message + * @param message.type + * @param message.data + * @param message.context + * @param thoughtSpotHost + * @param channel + */ +function postIframeMessage(iFrame, message, thoughtSpotHost, channel) { + return iFrame.contentWindow?.postMessage(message, thoughtSpotHost, [channel?.port2]); +} +const TRIGGER_TIMEOUT = 30000; +/** + * + * @param iFrame + * @param messageType + * @param thoughtSpotHost + * @param data + * @param context + */ +function processTrigger(iFrame, messageType, thoughtSpotHost, data, context) { + return new Promise((res, rej) => { + if (messageType === HostEvent.Reload) { + reload(iFrame); + return res(null); + } + if (messageType === HostEvent.Present) { + const embedConfig = getEmbedConfig(); + const disableFullscreenPresentation = embedConfig?.disableFullscreenPresentation ?? true; + if (!disableFullscreenPresentation) { + handlePresentEvent(iFrame); + } + else { + logger$3.warn('Fullscreen presentation mode is disabled. Set disableFullscreenPresentation: false to enable this feature.'); + } + } + const channel = new MessageChannel(); + channel.port1.onmessage = ({ data: responseData }) => { + channel.port1.close(); + const error = responseData?.error || responseData?.data?.error; + if (error) { + rej(error); + } + else { + res(responseData); + } + }; + // Close the messageChannel and resolve the promise if timeout. + setTimeout(() => { + channel.port1.close(); + res(new Error(ERROR_MESSAGE.TRIGGER_TIMED_OUT)); + }, TRIGGER_TIMEOUT); + return postIframeMessage(iFrame, { type: messageType, data, context }, thoughtSpotHost, channel); + }); +} + +/** + * Copyright (c) 2022 + * + * Base classes + * @summary Base classes + * @author Ayon Ghosh + */ +const CONFIG_DEFAULTS = { + loginFailedMessage: 'Not logged in', + authTriggerText: 'Authorize', + authType: AuthType.None, + logLevel: LogLevel.ERROR, + waitForCleanupOnDestroy: false, + cleanupTimeout: 5000, +}; +let authPromise; +const getAuthPromise = () => authPromise; +/** + * Perform authentication on the ThoughtSpot app as applicable. + */ +const handleAuth = () => { + authPromise = authenticate(getEmbedConfig()); + authPromise.then((isLoggedIn) => { + if (!isLoggedIn) { + notifyAuthFailure(AuthFailureType.SDK); + } + else { + // Post login service is called after successful login. + postLoginService(); + notifyAuthSDKSuccess(); + } + }, () => { + notifyAuthFailure(AuthFailureType.SDK); + }); + return authPromise; +}; +const hostUrlToFeatureUrl = { + [PrefetchFeatures.SearchEmbed]: (url, flags) => `${url}v2/?${flags}#/embed/answer`, + [PrefetchFeatures.LiveboardEmbed]: (url, flags) => `${url}?${flags}`, + [PrefetchFeatures.FullApp]: (url, flags) => `${url}?${flags}`, + [PrefetchFeatures.VizEmbed]: (url, flags) => `${url}?${flags}`, +}; +/** + * Prefetches static resources from the specified URL. Web browsers can then cache the + * prefetched resources and serve them from the user's local disk to provide faster access + * to your app. + * @param url The URL provided for prefetch + * @param prefetchFeatures Specify features which needs to be prefetched. + * @param additionalFlags This can be used to add any URL flag. + * @version SDK: 1.4.0 | ThoughtSpot: ts7.sep.cl, 7.2.1 + * @group Global methods + */ +const prefetch = (url, prefetchFeatures, additionalFlags) => { + if (url === '') { + logger$3.warn('The prefetch method does not have a valid URL'); + } + else { + const features = prefetchFeatures || [PrefetchFeatures.FullApp]; + let hostUrl = url || getEmbedConfig().thoughtSpotHost; + const prefetchFlags = { + [Param.EmbedApp]: true, + ...getEmbedConfig()?.additionalFlags, + ...additionalFlags, + }; + hostUrl = hostUrl[hostUrl.length - 1] === '/' ? hostUrl : `${hostUrl}/`; + Array.from(new Set(features + .map((feature) => hostUrlToFeatureUrl[feature](hostUrl, getQueryParamString(prefetchFlags))))) + .forEach((prefetchUrl, index) => { + const iFrame = document.createElement('iframe'); + iFrame.src = prefetchUrl; + iFrame.style.width = '0'; + iFrame.style.height = '0'; + iFrame.style.border = '0'; + // Make it 'fixed' to keep it in a different stacking + // context. This should solve the focus behaviours inside + // the iframe from interfering with main body. + iFrame.style.position = 'fixed'; + // Push it out of viewport. + iFrame.style.top = '100vh'; + iFrame.style.left = '100vw'; + iFrame.classList.add('prefetchIframe'); + iFrame.classList.add(`prefetchIframeNum-${index}`); + document.body.appendChild(iFrame); + }); + } +}; +/** + * + * @param embedConfig + */ +function sanity(embedConfig) { + if (embedConfig.thoughtSpotHost === undefined) { + throw new Error('ThoughtSpot host not provided'); + } + if (embedConfig.authType === AuthType.TrustedAuthToken) { + if (!embedConfig.authEndpoint && typeof embedConfig.getAuthToken !== 'function') { + throw new Error('Trusted auth should provide either authEndpoint or getAuthToken'); + } + } +} +/** + * + * @param embedConfig + */ +function backwardCompat(embedConfig) { + const newConfig = { ...embedConfig }; + if (embedConfig.noRedirect !== undefined && embedConfig.inPopup === undefined) { + newConfig.inPopup = embedConfig.noRedirect; + } + return newConfig; +} +const initFlagKey = 'initFlagKey'; +const createAndSetInitPromise = () => { + if (isWindowUndefined()) + return; + const { promise: initPromise, resolve: initPromiseResolve, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + } = Promise.withResolvers(); + const initFlagStore = { + initPromise, + isInitCalled: false, + isInitCompleted: false, + initPromiseResolve, + }; + storeValueInWindow(initFlagKey, initFlagStore, { + // In case of diff imports the promise might be already set + ignoreIfAlreadyExists: true, + }); + initPromise.finally(() => { + const curVal = getValueFromWindow(initFlagKey); + curVal.isInitCompleted = true; + storeValueInWindow(initFlagKey, curVal); + }); +}; +createAndSetInitPromise(); +const getInitPromise = () => getValueFromWindow(initFlagKey)?.initPromise; +const getIsInitCompleted = () => getValueFromWindow(initFlagKey)?.isInitCompleted; +const getIsInitCalled = () => !!getValueFromWindow(initFlagKey)?.isInitCalled; +/** + * Initializes the Visual Embed SDK globally and perform + * authentication if applicable. This function needs to be called before any ThoughtSpot + * component like Liveboard etc can be embedded. But need not wait for AuthEvent.SUCCESS + * to actually embed. That is handled internally. + * @param embedConfig The configuration object containing ThoughtSpot host, + * authentication mechanism and so on. + * @example + * ```js + * const authStatus = init({ + * thoughtSpotHost: 'https://my.thoughtspot.cloud', + * authType: AuthType.None, + * }); + * authStatus.on(AuthStatus.FAILURE, (reason) => { // do something here }); + * ``` + * @returns {@link AuthEventEmitter} event emitter which emits events on authentication success, + * failure and logout. See {@link AuthStatus} + * @version SDK: 1.0.0 | ThoughtSpot ts7.april.cl, 7.2.1 + * @group Authentication / Init + */ +const init = (embedConfig) => { + if (isWindowUndefined()) + return null; + sanity(embedConfig); + resetAllCachedServices(); + embedConfig = setEmbedConfig(backwardCompat({ + ...CONFIG_DEFAULTS, + ...embedConfig, + thoughtSpotHost: getThoughtSpotHost(embedConfig), + })); + setGlobalLogLevelOverride(embedConfig.logLevel); + registerReportingObserver(); + const authEE = new eventemitter3(); + setAuthEE(authEE); + handleAuth(); + const { password, ...configToTrack } = getEmbedConfig(); + uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_CALLED_INIT, { + ...configToTrack, + usedCustomizationSheet: embedConfig.customizations?.style?.customCSSUrl != null, + usedCustomizationVariables: embedConfig.customizations?.style?.customCSS?.variables != null, + usedCustomizationRules: embedConfig.customizations?.style?.customCSS?.rules_UNSTABLE != null, + usedCustomizationStrings: !!embedConfig.customizations?.content?.strings, + usedCustomizationIconSprite: !!embedConfig.customizations?.iconSpriteUrl, + }); + if (getEmbedConfig().callPrefetch) { + prefetch(getEmbedConfig().thoughtSpotHost); + } + // Resolves the promise created in the initPromiseKey + getValueFromWindow(initFlagKey).initPromiseResolve(authEE); + getValueFromWindow(initFlagKey).isInitCalled = true; + return authEE; +}; +/** + * + */ +function disableAutoLogin() { + getEmbedConfig().autoLogin = false; +} +/** + * Logs out from ThoughtSpot. This also sets the autoLogin flag to false, to + * prevent the SDK from automatically logging in again. + * + * You can call the `init` method again to re login, if autoLogin is set to + * true in this second call it will be honored. + * @param doNotDisableAutoLogin This flag when passed will not disable autoLogin + * @returns Promise which resolves when logout completes. + * @version SDK: 1.10.1 | ThoughtSpot: 8.2.0.cl, 8.4.1-sw + * @group Global methods + */ +const logout = (doNotDisableAutoLogin = false) => { + if (!doNotDisableAutoLogin) { + disableAutoLogin(); + } + return logout$1(getEmbedConfig()).then((isLoggedIn) => { + notifyLogout(); + return isLoggedIn; + }); +}; +let renderQueue = Promise.resolve(); +/** + * Renders functions in a queue, resolves to next function only after the callback next + * is called + * @param fn The function being registered + */ +const renderInQueue = (fn) => { + const { queueMultiRenders = false } = getEmbedConfig(); + if (queueMultiRenders) { + renderQueue = renderQueue.then(() => new Promise((res) => fn(res))); + return renderQueue; + } + // Sending an empty function to keep it consistent with the above usage. + return fn(() => { }); +}; +/** + * Imports TML representation of the metadata objects into ThoughtSpot. + * @param data + * @returns imports TML data into ThoughtSpot + * @example + * ```js + * executeTML({ + * //Array of metadata Tmls in string format + * metadata_tmls: [ + * "'\''{\"guid\":\"9bd202f5-d431-44bf-9a07-b4f7be372125\", + * \"liveboard\":{\"name\":\"Parameters Liveboard\"}}'\''" + * ], + * import_policy: 'PARTIAL', // Specifies the import policy for the TML import. + * create_new: false, // If selected, creates TML objects with new GUIDs. + * }).then(result => { + * console.log(result); + * }).catch(error => { + * console.error(error); + * }); + * ``` + * @version SDK: 1.23.0 | ThoughtSpot: 9.4.0.cl + * @group Global methods + */ +const executeTML = async (data) => { + try { + sanity(getEmbedConfig()); + } + catch (err) { + return Promise.reject(err); + } + const { thoughtSpotHost, authType } = getEmbedConfig(); + const headers = { + 'Content-Type': 'application/json', + 'x-requested-by': 'ThoughtSpot', + }; + const payload = { + metadata_tmls: data.metadata_tmls, + import_policy: data.import_policy || 'PARTIAL', + create_new: data.create_new || false, + }; + return tokenizedFetch(`${thoughtSpotHost}${EndPoints.EXECUTE_TML}`, { + method: 'POST', + headers, + body: JSON.stringify(payload), + credentials: 'include', + }) + .then((response) => { + if (!response.ok) { + throw new Error(`Failed to import TML data: ${response.status} - ${response.statusText}`); + } + return response.json(); + }) + .catch((error) => { + throw error; + }); +}; +/** + * Exports TML representation of the metadata objects from ThoughtSpot in JSON or YAML + * format. + * @param data + * @returns exports TML data + * @example + * ```js + * exportTML({ + * metadata: [ + * { + * type: "LIVEBOARD", //Metadata Type + * identifier: "9bd202f5-d431-44bf-9a07-b4f7be372125" //Metadata Id + * } + * ], + * export_associated: false,//indicates whether to export associated metadata objects + * export_fqn: false, //Adds FQNs of the referenced objects.For example, if you are + * //exporting a Liveboard and its associated objects, the API + * //returns the Liveboard TML data with the FQNs of the referenced + * //worksheet. If the exported TML data includes FQNs, you don't need + * //to manually add FQNs of the referenced objects during TML import. + * edoc_format: "JSON" //It takes JSON or YAML value + * }).then(result => { + * console.log(result); + * }).catch(error => { + * console.error(error); + * }); + * ``` + * @version SDK: 1.23.0 | ThoughtSpot: 9.4.0.cl + * @group Global methods + */ +const exportTML = async (data) => { + const { thoughtSpotHost, authType } = getEmbedConfig(); + try { + sanity(getEmbedConfig()); + } + catch (err) { + return Promise.reject(err); + } + const payload = { + metadata: data.metadata, + export_associated: data.export_associated || false, + export_fqn: data.export_fqn || false, + edoc_format: data.edoc_format || 'YAML', + }; + const headers = { + 'Content-Type': 'application/json', + 'x-requested-by': 'ThoughtSpot', + }; + return tokenizedFetch(`${thoughtSpotHost}${EndPoints.EXPORT_TML}`, { + method: 'POST', + headers, + body: JSON.stringify(payload), + credentials: 'include', + }) + .then((response) => { + if (!response.ok) { + throw new Error(`Failed to export TML: ${response.status} - ${response.statusText}`); + } + return response.json(); + }) + .catch((error) => { + throw error; + }); +}; +/** + * Reloads the ThoughtSpot iframe. + * @version SDK: 1.43.1 + * @param iFrame + * @group Global methods + */ +const reloadIframe = (iFrame) => { + if (!iFrame) { + logger$3.warn('reloadIframe called with no iFrame element.'); + return; + } + reload(iFrame); +}; + +/** + * Process the ExitPresentMode event and handle default fullscreen exit + * @param e - The event data + */ +function processExitPresentMode(e) { + const embedConfig = getEmbedConfig(); + const disableFullscreenPresentation = embedConfig?.disableFullscreenPresentation ?? true; + if (!disableFullscreenPresentation) { + handleExitPresentMode(); + } +} +/** + * Clears the cached preauth and session info. + */ +function processClearInfoCache() { + resetCachedPreauthInfo(); + resetCachedSessionInfo(); +} +/** + * + * @param e + * @param thoughtSpotHost + */ +function processCustomAction(e, thoughtSpotHost) { + const { session, embedAnswerData, contextMenuPoints } = e.data; + const answerService = new AnswerService(session, embedAnswerData || {}, thoughtSpotHost, contextMenuPoints?.selectedPoints); + return { + ...e, + answerService, + }; +} +/** + * Responds to AuthInit sent from host signifying successful authentication in host. + * @param e + * @returns {any} + */ +function processAuthInit(e) { + notifyAuthSuccess(); + // Expose only allowed details (eg: userGUID) back to SDK users. + return { + ...e, + data: { + userGUID: e.data?.userGUID || e.payload?.userGUID, + }, + }; +} +/** + * + * @param e + * @param containerEl + */ +function processNoCookieAccess(e, containerEl) { + const { loginFailedMessage, suppressNoCookieAccessAlert, ignoreNoCookieAccess, suppressErrorAlerts, } = getEmbedConfig(); + if (!ignoreNoCookieAccess) { + if (!suppressNoCookieAccessAlert && !suppressErrorAlerts) { + alert(ERROR_MESSAGE.THIRD_PARTY_COOKIE_BLOCKED_ALERT); + } + containerEl.innerHTML = loginFailedMessage; + } + notifyAuthFailure(AuthFailureType.NO_COOKIE_ACCESS); + return e; +} +/** + * + * @param e + * @param containerEl + */ +function processAuthFailure(e, containerEl) { + const { loginFailedMessage, authType, disableLoginFailurePage, autoLogin, } = getEmbedConfig(); + const isEmbeddedSSO = authType === AuthType.EmbeddedSSO; + const isTrustedAuth = authType === AuthType.TrustedAuthToken || authType === AuthType.TrustedAuthTokenCookieless; + const isEmbeddedSSOInfoFailure = isEmbeddedSSO && e?.data?.type === AuthFailureType.UNAUTHENTICATED_FAILURE; + if (autoLogin && isTrustedAuth) { + containerEl.innerHTML = loginFailedMessage; + notifyAuthFailure(AuthFailureType.IDLE_SESSION_TIMEOUT); + } + else if (authType !== AuthType.None && !disableLoginFailurePage && !isEmbeddedSSOInfoFailure) { + containerEl.innerHTML = loginFailedMessage; + notifyAuthFailure(AuthFailureType.OTHER); + } + resetCachedAuthToken(); + return e; +} +/** + * + * @param e + * @param containerEl + */ +function processAuthLogout(e, containerEl) { + const { loginFailedMessage } = getEmbedConfig(); + containerEl.innerHTML = loginFailedMessage; + resetCachedAuthToken(); + disableAutoLogin(); + notifyLogout(); + return e; +} +/** + * + * @param type + * @param e + * @param thoughtSpotHost + * @param containerEl + */ +function processEventData(type, eventData, thoughtSpotHost, containerEl) { + switch (type) { + case EmbedEvent.CustomAction: + return processCustomAction(eventData, thoughtSpotHost); + case EmbedEvent.AuthInit: + return processAuthInit(eventData); + case EmbedEvent.NoCookieAccess: + return processNoCookieAccess(eventData, containerEl); + case EmbedEvent.AuthFailure: + return processAuthFailure(eventData, containerEl); + case EmbedEvent.AuthLogout: + return processAuthLogout(eventData, containerEl); + case EmbedEvent.ExitPresentMode: + return processExitPresentMode(); + case EmbedEvent.CLEAR_INFO_CACHE: + return processClearInfoCache(); + } + return eventData; +} + +function isValidUpdateFiltersPayload(payload) { + if (!payload) + return false; + const isValidFilter = (f) => { + const hasColumn = typeof f.column === 'string' || typeof f.columnName === 'string'; + const hasOperator = typeof f.oper === 'string' || typeof f.operator === 'string'; + const hasValues = Array.isArray(f.values); + const validType = !f.type || typeof f.type === 'string'; + return hasColumn && hasOperator && hasValues && validType; + }; + const hasValidFilter = payload.filter && isValidFilter(payload.filter); + const hasValidFilters = Array.isArray(payload.filters) && payload.filters.length > 0 && payload.filters.every(isValidFilter); + return !!(hasValidFilter || hasValidFilters); +} +function isValidDrillDownPayload(payload) { + if (!payload) + return false; + const points = payload.points; + if (!points || typeof points !== 'object') + return false; + const hasClickedPoint = 'clickedPoint' in points && points.clickedPoint != null; + const hasSelectedPoints = Array.isArray(points.selectedPoints) && points.selectedPoints.length > 0; + return hasClickedPoint || hasSelectedPoints; +} +function createValidationError(message) { + const err = new Error(message); + err.isValidationError = true; + err.embedErrorDetails = { + type: EmbedEvent.Error, + data: { + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message, + code: EmbedErrorCodes.HOST_EVENT_VALIDATION, + error: message + }, + status: embedEventStatus.END + }; + throw err; +} +function throwUpdateFiltersValidationError() { + createValidationError(ERROR_MESSAGE.UPDATEFILTERS_INVALID_PAYLOAD); +} +function throwDrillDownValidationError() { + createValidationError(ERROR_MESSAGE.DRILLDOWN_INVALID_PAYLOAD); +} + +/** + * Maps HostEvent to its corresponding UIPassthroughEvent. + * Includes both custom-handler events (Pin, SaveAnswer, UpdateFilters, DrillDown) + * and getter events (GetAnswerSession, GetFilters, etc.) that use getDataWithPassthroughFallback. + */ +const PASSTHROUGH_MAP = { + // Custom handlers (setters with special logic) + [HostEvent.Pin]: UIPassthroughEvent.PinAnswerToLiveboard, + [HostEvent.SaveAnswer]: UIPassthroughEvent.SaveAnswer, + [HostEvent.UpdateFilters]: UIPassthroughEvent.UpdateFilters, + [HostEvent.DrillDown]: UIPassthroughEvent.Drilldown, + // Getters (use getDataWithPassthroughFallback) + [HostEvent.GetAnswerSession]: UIPassthroughEvent.GetAnswerSession, + [HostEvent.GetFilters]: UIPassthroughEvent.GetFilters, + [HostEvent.GetIframeUrl]: UIPassthroughEvent.GetIframeUrl, + [HostEvent.GetParameters]: UIPassthroughEvent.GetParameters, + [HostEvent.GetTML]: UIPassthroughEvent.GetTML, + [HostEvent.GetTabs]: UIPassthroughEvent.GetTabs, + [HostEvent.getExportRequestForCurrentPinboard]: UIPassthroughEvent.GetExportRequestForCurrentPinboard, +}; +class HostEventClient { + constructor(iFrame) { + /** Cached list of available UI passthrough keys from the embedded app */ + this.availablePassthroughKeysCache = null; + this.iFrame = iFrame; + this.customHandlers = { + [HostEvent.Pin]: (p, c) => this.handlePinEvent(p, c), + [HostEvent.SaveAnswer]: (p, c) => this.handleSaveAnswerEvent(p, c), + [HostEvent.UpdateFilters]: (p, c) => this.handleUpdateFiltersEvent(p, c), + [HostEvent.DrillDown]: (p, c) => this.handleDrillDownEvent(p, c), + }; + } + /** + * A wrapper over process trigger to + * @param {HostEvent} message Host event to send + * @param {any} data Data to send with the host event + * @returns {Promise} - the response from the process trigger + */ + async processTrigger(message, data, context) { + if (!this.iFrame) { + throw new Error('Iframe element is not set'); + } + const thoughtspotHost = getEmbedConfig().thoughtSpotHost; + return processTrigger(this.iFrame, message, thoughtspotHost, data, context); + } + async handleHostEventWithParam(apiName, parameters, context) { + const response = (await this.triggerUIPassthroughApi(apiName, parameters, context)) + ?.find?.((r) => r.error || r.value); + if (!response) { + const error = `No answer found${parameters.vizId ? ` for vizId: ${parameters.vizId}` : ''}.`; + throw { error }; + } + const errors = response.error + || response.value?.errors + || response.value?.error; + if (errors) { + const message = typeof errors === 'string' ? errors : JSON.stringify(errors); + throw { error: message }; + } + return { ...response.value }; + } + async hostEventFallback(hostEvent, data, context) { + return this.processTrigger(hostEvent, data, context); + } + /** + * For getter events that return data. Tries UI passthrough first; + * if the app doesn't support it (no response data), falls back to + * the legacy host event channel. Real errors are thrown as-is. + */ + async getDataWithPassthroughFallback(passthroughEvent, hostEvent, payload, context) { + const response = await this.triggerUIPassthroughApi(passthroughEvent, payload || {}, context); + const matched = response?.find?.((r) => r.error || r.value); + if (!matched) { + return this.hostEventFallback(hostEvent, payload, context); + } + const errors = matched.error + || matched.value?.errors + || matched.value?.error; + if (errors) { + const message = typeof errors === 'string' ? errors : JSON.stringify(errors); + throw new Error(message); + } + return { ...matched.value }; + } + /** + * Setter for the iframe element used for host events + * @param {HTMLIFrameElement} iFrame - the iframe element to set + */ + setIframeElement(iFrame) { + this.iFrame = iFrame; + } + /** + * Fetches the list of available UI passthrough keys from the embedded app. + * Result is cached for the session. Returns empty array on failure. + */ + async getAvailableUIPassthroughKeys(context) { + if (this.availablePassthroughKeysCache !== null) { + return this.availablePassthroughKeysCache; + } + try { + const response = await this.triggerUIPassthroughApi(UIPassthroughEvent.GetAvailableUIPassthroughs, {}, context); + const matched = response?.find?.((r) => r.value && !r.error); + const keys = matched?.value?.keys; + this.availablePassthroughKeysCache = Array.isArray(keys) ? keys : []; + return this.availablePassthroughKeysCache; + } + catch { + return []; + } + } + async triggerUIPassthroughApi(apiName, parameters, context) { + const res = await this.processTrigger(HostEvent.UIPassthrough, { + type: apiName, + parameters, + }, context); + return res; + } + async handlePinEvent(payload, context) { + if (!payload || !('newVizName' in payload)) { + return this.hostEventFallback(HostEvent.Pin, payload, context); + } + const formattedPayload = { + ...payload, + pinboardId: payload.liveboardId ?? payload.pinboardId, + newPinboardName: payload.newLiveboardName ?? payload.newPinboardName, + }; + const data = await this.handleHostEventWithParam(UIPassthroughEvent.PinAnswerToLiveboard, formattedPayload, context); + return { + ...data, + liveboardId: data.pinboardId, + }; + } + async handleSaveAnswerEvent(payload, context) { + if (!payload || !('name' in payload) || !('description' in payload)) { + // Save is the fallback for SaveAnswer + return this.hostEventFallback(HostEvent.Save, payload, context); + } + const data = await this.handleHostEventWithParam(UIPassthroughEvent.SaveAnswer, payload, context); + return { + ...data, + answerId: data?.saveResponse?.data?.Answer__save?.answer?.id, + }; + } + handleUpdateFiltersEvent(payload, context) { + if (!isValidUpdateFiltersPayload(payload)) { + throwUpdateFiltersValidationError(); + } + return this.handleHostEventWithParam(UIPassthroughEvent.UpdateFilters, payload, context); + } + handleDrillDownEvent(payload, context) { + if (!isValidDrillDownPayload(payload)) { + throwDrillDownValidationError(); + } + return this.handleHostEventWithParam(UIPassthroughEvent.Drilldown, payload, context); + } + /** + * Dispatches a host event using the appropriate channel: + * 1. If the embedded app supports UI passthrough for this event, use it (custom handler or getter). + * 2. Otherwise fall back to the legacy host event channel. + * + * @param hostEvent - The host event to trigger + * @param payload - Optional payload for the event + * @param context - Optional context (e.g. vizId) for scoped operations + */ + async triggerHostEvent(hostEvent, payload, context) { + const customHandler = this.customHandlers[hostEvent]; + const passthroughEvent = PASSTHROUGH_MAP[hostEvent]; + // If embedded app supports passthrough but not this event, use legacy channel + const keys = passthroughEvent ? await this.getAvailableUIPassthroughKeys(context) : []; + if (passthroughEvent && keys.length > 0 && !keys.includes(passthroughEvent)) { + return this.hostEventFallback(hostEvent, payload, context); + } + // Custom handler (setters) > getter passthrough > legacy fallback + return (customHandler + ? customHandler(payload, context) + : passthroughEvent + ? this.getDataWithPassthroughFallback(passthroughEvent, hostEvent, payload, context) + : this.hostEventFallback(hostEvent, payload, context)); + } +} + +const DefaultInterceptUrlsMap = { + [InterceptedApiType.AnswerData]: [ + '/prism/?op=GetChartWithData', + '/prism/?op=GetTableWithHeadlineData', + '/prism/?op=GetTableWithData', + ], + [InterceptedApiType.LiveboardData]: [ + '/prism/?op=LoadContextBook' + ], +}; +const formatInterceptUrl = (url) => { + const host = getThoughtSpotHost(getEmbedConfig()); + if (url.startsWith('/')) + return `${host}${url}`; + return url; +}; +/** + * Converts user passed url values to proper urls + * [ANSER_DATA] => ['https://host/pris/op?=op'] + * @param interceptUrls + * @returns + */ +const processInterceptUrls = (interceptUrls) => { + let processedUrls = [...interceptUrls]; + Object.entries(DefaultInterceptUrlsMap).forEach(([apiType, apiTypeUrls]) => { + if (!processedUrls.includes(apiType)) + return; + processedUrls = processedUrls.filter(url => url !== apiType); + processedUrls = [...processedUrls, ...apiTypeUrls]; + }); + return processedUrls.map(url => formatInterceptUrl(url)); +}; +/** + * Returns the data to be sent to embed to setup intercepts + * the urls to intercept, timeout etc + * @param viewConfig + * @returns + */ +const getInterceptInitData = (viewConfig) => { + const combinedUrls = [...(viewConfig.interceptUrls || [])]; + if (viewConfig.isOnBeforeGetVizDataInterceptEnabled) { + combinedUrls.push(InterceptedApiType.AnswerData); + } + const shouldInterceptAll = combinedUrls.includes(InterceptedApiType.ALL); + const interceptUrls = shouldInterceptAll ? [InterceptedApiType.ALL] : processInterceptUrls(combinedUrls); + const interceptTimeout = viewConfig.interceptTimeout; + return { + interceptUrls, + interceptTimeout, + }; +}; +const parseJson = (jsonString) => { + try { + const json = JSON.parse(jsonString); + return [json, null]; + } + catch (error) { + return [null, error]; + } +}; +/** + * Parse the api intercept data and return the parsed data and error if any + * Embed returns the input and init from the fetch call + */ +const parseInterceptData = (eventDataString) => { + try { + const [parsedData, error] = parseJson(eventDataString); + if (error) { + return [null, error]; + } + const { input, init } = parsedData; + const [parsedBody, bodyParseError] = parseJson(init.body); + if (!bodyParseError) { + init.body = parsedBody; + } + const parsedInit = { input, init }; + return [parsedInit, null]; + } + catch (error) { + return [null, error]; + } +}; +const getUrlType = (url) => { + for (const [apiType, apiTypeUrls] of Object.entries(DefaultInterceptUrlsMap)) { + if (apiTypeUrls.includes(url)) + return apiType; + } + // TODO: have a unknown type maybe ?? + return InterceptedApiType.ALL; +}; +/** + * Handle Api intercept event and simulate legacy onBeforeGetVizDataIntercept event + * + * embed sends -> ApiIntercept -> we send + * ApiIntercept + * OnBeforeGetVizDataIntercept (if url is part of DefaultUrlMap.AnswerData) + * + * @param params + * @returns + */ +const handleInterceptEvent = async (params) => { + const { eventData, executeEvent, viewConfig, getUnsavedAnswerTml } = params; + const [interceptData, bodyParseError] = parseInterceptData(eventData.data); + if (bodyParseError) { + const errorDetails = { + errorType: ErrorDetailsTypes.API, + message: ERROR_MESSAGE.ERROR_PARSING_API_INTERCEPT_BODY, + code: EmbedErrorCodes.PARSING_API_INTERCEPT_BODY_ERROR, + error: ERROR_MESSAGE.ERROR_PARSING_API_INTERCEPT_BODY, + }; + executeEvent(EmbedEvent.Error, errorDetails); + logger$3.error('Error parsing request body', bodyParseError); + return; + } + const { input: requestUrl, init } = interceptData; + const sessionId = init?.body?.variables?.session?.sessionId; + const vizId = init?.body?.variables?.contextBookId; + const answerDataUrls = DefaultInterceptUrlsMap[InterceptedApiType.AnswerData]; + const legacyInterceptEnabled = viewConfig.isOnBeforeGetVizDataInterceptEnabled; + const isAnswerDataUrl = answerDataUrls.includes(requestUrl); + const sendLegacyIntercept = isAnswerDataUrl && legacyInterceptEnabled; + if (sendLegacyIntercept) { + const answerTml = await getUnsavedAnswerTml({ sessionId, vizId }); + // Build the legacy payload for backwards compatibility + const legacyPayload = { + data: { + data: answerTml, + status: embedEventStatus.END, + type: EmbedEvent.OnBeforeGetVizDataIntercept + } + }; + executeEvent(EmbedEvent.OnBeforeGetVizDataIntercept, legacyPayload); + } + const urlType = getUrlType(requestUrl); + executeEvent(EmbedEvent.ApiIntercept, { ...interceptData, urlType }); +}; +/** + * Support both the legacy and new format of the api intercept response + * @param payload + * @returns + */ +const processApiInterceptResponse = (payload) => { + const isLegacyFormat = payload?.data?.error; + if (isLegacyFormat) { + return processLegacyInterceptResponse(payload); + } + return payload; +}; +const processLegacyInterceptResponse = (payload) => { + const errorText = payload?.data?.error?.errorText; + const errorDescription = payload?.data?.error?.errorDescription; + const payloadToSend = { + execute: payload?.data?.execute, + response: { + body: { + errors: [ + { + title: errorText, + description: errorDescription, + isUserError: true, + }, + ], + data: {}, + }, + }, + }; + return { data: payloadToSend }; +}; + +/** + * Copyright (c) 2022 + * + * Base classes + * @summary Base classes + * @author Ayon Ghosh + */ +/** + * Global prefix for all ThoughtSpot postHash Params. + */ +const THOUGHTSPOT_PARAM_PREFIX = 'ts-'; +const TS_EMBED_ID = '_thoughtspot-embed'; +const VERSION = packageInfo.version; +/** + * The event id map from v2 event names to v1 event id + * v1 events are the classic embed events implemented in Blink v1 + * We cannot rename v1 event types to maintain backward compatibility + * @internal + */ +const V1EventMap = {}; +/** + * Base class for embedding v2 experience + * Note: the v2 version of ThoughtSpot Blink is built on the new stack: + * React+GraphQL + */ +class TsEmbed { + /** + * Setter for the iframe element + * @param {HTMLIFrameElement} iFrame HTMLIFrameElement + */ + setIframeElement(iFrame) { + this.iFrame = iFrame; + this.hostEventClient.setIframeElement(iFrame); + } + constructor(domSelector, viewConfig) { + /** + * The key to store the embed instance in the DOM node + */ + this.embedNodeKey = '__tsEmbed'; + this.isAppInitialized = false; + /** + * Should we encode URL Query Params using base64 encoding which ThoughtSpot + * will generate for embedding. This provides additional security to + * ThoughtSpot clusters against Cross site scripting attacks. + * @default false + */ + this.shouldEncodeUrlQueryParams = false; + this.defaultHiddenActions = [Action.ReportError]; + /** + * Handler for fullscreen change events + */ + this.fullscreenChangeHandler = null; + this.subscribedListeners = {}; + this.messageEventListener = (event) => { + const eventType = this.getEventType(event); + const eventPort = this.getEventPort(event); + const eventData = this.formatEventData(event, eventType); + if (event.source === this.iFrame.contentWindow) { + const processedEventData = processEventData(eventType, eventData, this.thoughtSpotHost, this.isPreRendered ? this.preRenderWrapper : this.hostElement); + if (eventType === EmbedEvent.ApiIntercept) { + this.handleApiInterceptEvent({ eventData, eventPort }); + return; + } + this.executeCallbacks(eventType, processedEventData, eventPort); + } + }; + /** + * Send Custom style as part of payload of APP_INIT + * @param _ + * @param responder + */ + this.appInitCb = async (_, responder) => { + try { + const appInitData = await this.getAppInitData(); + this.isAppInitialized = true; + responder({ + type: EmbedEvent.APP_INIT, + data: appInitData, + }); + } + catch (e) { + logger$3.error(`AppInit failed, Error : ${e?.message}`); + } + }; + this.handleAuthFailure = (error) => { + logger$3.error(`${ERROR_MESSAGE.INVALID_TOKEN_ERROR} Error : ${error?.message}`); + processAuthFailure(error, this.isPreRendered ? this.preRenderWrapper : this.hostElement); + }; + /** + * Refresh the auth token if the autoLogin is true and the authType is TrustedAuthTokenCookieless + * @param _ + * @param responder + */ + this.tokenRefresh = async (_, responder) => { + try { + await this.refreshAuthTokenForCookieless(responder, EmbedEvent.RefreshAuthToken, true); + } + catch (e) { + this.handleAuthFailure(e); + } + }; + /** + * Sends updated auth token to the iFrame to avoid user logout + * @param _ + * @param responder + */ + this.updateAuthToken = async (_, responder) => { + const { authType, autoLogin: autoLoginConfig } = this.embedConfig; + // Default autoLogin: true for cookieless if undefined/null, otherwise + // false + const autoLogin = autoLoginConfig ?? (authType === AuthType.TrustedAuthTokenCookieless); + try { + await this.refreshAuthTokenForCookieless(responder, EmbedEvent.AuthExpire, false); + } + catch (e) { + this.handleAuthFailure(e); + } + if (autoLogin && authType !== AuthType.TrustedAuthTokenCookieless) { + handleAuth(); + } + notifyAuthFailure(AuthFailureType.EXPIRY); + }; + /** + * Auto Login and send updated authToken to the iFrame to avoid user session logout + * @param _ + * @param responder + */ + this.idleSessionTimeout = (_, responder) => { + handleAuth().then(async () => { + let authToken = ''; + try { + authToken = await getAuthenticationToken(this.embedConfig); + responder({ + type: EmbedEvent.IdleSessionTimeout, + data: { authToken }, + }); + } + catch (e) { + this.handleAuthFailure(e); + } + }).catch((e) => { + logger$3.error(`Auto Login failed, Error : ${e?.message}`); + }); + notifyAuthFailure(AuthFailureType.IDLE_SESSION_TIMEOUT); + }; + /** + * Register APP_INIT event and sendback init payload + */ + this.registerAppInit = () => { + this.on(EmbedEvent.APP_INIT, this.appInitCb, { start: false }, true); + this.on(EmbedEvent.AuthExpire, this.updateAuthToken, { start: false }, true); + this.on(EmbedEvent.IdleSessionTimeout, this.idleSessionTimeout, { start: false }, true); + const embedListenerReadyHandler = this.createEmbedContainerHandler(EmbedEvent.EmbedListenerReady); + this.on(EmbedEvent.EmbedListenerReady, embedListenerReadyHandler, { start: false }, true); + const authInitHandler = this.createEmbedContainerHandler(EmbedEvent.AuthInit); + this.on(EmbedEvent.AuthInit, authInitHandler, { start: false }, true); + this.on(EmbedEvent.RefreshAuthToken, this.tokenRefresh, { start: false }, true); + }; + this.showPreRenderByDefault = false; + /** + * We can process the customer given payload before sending it to the embed port + * Embed event handler -> responder -> createEmbedEventResponder -> send response + * @param eventPort The event port for a specific MessageChannel + * @param eventType The event type + * @returns + */ + this.createEmbedEventResponder = (eventPort, eventType) => { + const getPayloadToSend = (payload) => { + if (eventType === EmbedEvent.OnBeforeGetVizDataIntercept) { + return processLegacyInterceptResponse(payload); + } + if (eventType === EmbedEvent.ApiIntercept) { + return processApiInterceptResponse(payload); + } + return payload; + }; + return (payload) => { + const payloadToSend = getPayloadToSend(payload); + this.triggerEventOnPort(eventPort, payloadToSend); + }; + }; + /** + * @hidden + * Internal state to track if the embed container is loaded. + * This is used to trigger events after the embed container is loaded. + */ + this.isEmbedContainerLoaded = false; + /** + * @hidden + * Internal state to track the callbacks to be executed after the embed container + * is loaded. + * This is used to trigger events after the embed container is loaded. + */ + this.embedContainerReadyCallbacks = []; + this.createEmbedContainerHandler = (source) => () => { + const processEmbedContainerReady = () => { + logger$3.debug('processEmbedContainerReady'); + this.isEmbedContainerLoaded = true; + this.executeEmbedContainerReadyCallbacks(); + }; + if (source === EmbedEvent.AuthInit) { + const AUTH_INIT_FALLBACK_DELAY = 1000; + // Wait for 1 second to ensure the embed container is loaded + // This is a workaround to ensure the embed container is loaded + // this is needed until all clusters have EmbedListenerReady event + setTimeout(processEmbedContainerReady, AUTH_INIT_FALLBACK_DELAY); + } + else if (source === EmbedEvent.EmbedListenerReady) { + processEmbedContainerReady(); + } + }; + this.hostElement = getDOMNode(domSelector); + this.eventHandlerMap = new Map(); + this.isError = false; + this.viewConfig = { + excludeRuntimeFiltersfromURL: true, + excludeRuntimeParametersfromURL: true, + ...viewConfig, + }; + this.registerAppInit(); + uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_EMBED_CREATE, { + ...viewConfig, + sdkVersion: VERSION, + }); + const embedConfig = getEmbedConfig(); + if (embedConfig) { + this.embedConfig = embedConfig; + this.thoughtSpotHost = getThoughtSpotHost(embedConfig); + this.thoughtSpotV2Base = getV2BasePath(embedConfig); + } + this.hostEventClient = new HostEventClient(this.iFrame); + this.shouldWaitForRenderPromise = !getIsInitCompleted(); + const afterInit = () => { + this.embedConfig = embedConfig; + if (!embedConfig.authTriggerContainer && !embedConfig.useEventForSAMLPopup) { + this.embedConfig.authTriggerContainer = domSelector; + } + this.thoughtSpotHost = getThoughtSpotHost(embedConfig); + this.thoughtSpotV2Base = getV2BasePath(embedConfig); + this.shouldEncodeUrlQueryParams = embedConfig.shouldEncodeUrlQueryParams; + }; + if (!this.shouldWaitForRenderPromise) { + afterInit(); + } + else { + this.isReadyForRenderPromise = getInitPromise().then(afterInit).finally(() => { + this.shouldWaitForRenderPromise = true; + }); + } + } + /** + * Throws error encountered during initialization. + */ + throwInitError() { + this.handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.INIT_SDK_REQUIRED, + code: EmbedErrorCodes.INIT_ERROR, + error: ERROR_MESSAGE.INIT_SDK_REQUIRED, + }); + } + /** + * Handles errors within the SDK + * @param error The error message or object + * @param errorDetails The error details + */ + handleError(errorDetails) { + this.isError = true; + this.executeCallbacks(EmbedEvent.Error, errorDetails); + // Log error + logger$3.error(errorDetails); + } + /** + * Extracts the type field from the event payload + * @param event The window message event + */ + getEventType(event) { + return event.data?.type || event.data?.__type; + } + /** + * Extracts the port field from the event payload + * @param event The window message event + * @returns + */ + getEventPort(event) { + if (event.ports.length && event.ports[0]) { + return event.ports[0]; + } + return null; + } + /** + * Checks if preauth cache is enabled + * from the view config and embed config + * @returns boolean + */ + isPreAuthCacheEnabled() { + // Disable preauth cache when: + // 1. overrideOrgId is present since: + // - cached auth info would be for wrong org + // - info call response changes for each different overrideOrgId + // 2. disablePreauthCache is explicitly set to true + // 3. FullAppEmbed has primary navbar visible since: + // - primary navbar requires fresh auth state for navigation + // - cached auth may not reflect current user permissions + const isDisabled = (this.viewConfig.overrideOrgId !== undefined + || this.embedConfig.disablePreauthCache === true + || this.isFullAppEmbedWithVisiblePrimaryNavbar()); + return !isDisabled; + } + /** + * Checks if current embed is FullAppEmbed with visible primary navbar + * @returns boolean + */ + isFullAppEmbedWithVisiblePrimaryNavbar() { + const appViewConfig = this.viewConfig; + // Check if this is a FullAppEmbed (AppEmbed) + // showPrimaryNavbar defaults to true if not explicitly set to false + return (appViewConfig.embedComponentType === 'AppEmbed' + && appViewConfig.showPrimaryNavbar === true); + } + /** + * fix for ts7.sep.cl + * will be removed for ts7.oct.cl + * @param event + * @param eventType + * @hidden + */ + formatEventData(event, eventType) { + const eventData = { + ...event.data, + type: eventType, + }; + if (!eventData.data) { + eventData.data = event.data.payload; + } + return eventData; + } + /** + * Subscribe to network events (online/offline) that should + * work regardless of auth status + */ + subscribeToNetworkEvents() { + this.unsubscribeToNetworkEvents(); + const onlineEventListener = (e) => { + this.trigger(HostEvent.Reload); + }; + window.addEventListener('online', onlineEventListener); + const offlineEventListener = (e) => { + const errorDetails = { + errorType: ErrorDetailsTypes.NETWORK, + message: ERROR_MESSAGE.OFFLINE_WARNING, + code: EmbedErrorCodes.NETWORK_ERROR, + offlineWarning: ERROR_MESSAGE.OFFLINE_WARNING, + }; + this.executeCallbacks(EmbedEvent.Error, errorDetails); + logger$3.warn(errorDetails); + }; + window.addEventListener('offline', offlineEventListener); + this.subscribedListeners.online = onlineEventListener; + this.subscribedListeners.offline = offlineEventListener; + } + handleApiInterceptEvent({ eventData, eventPort }) { + const executeEvent = (_eventType, data) => { + this.executeCallbacks(_eventType, data, eventPort); + }; + const getUnsavedAnswerTml = async (props) => { + const response = await this.triggerUIPassThrough(UIPassthroughEvent.GetUnsavedAnswerTML, props); + return response.filter((item) => item.value)?.[0]?.value; + }; + handleInterceptEvent({ eventData, executeEvent, viewConfig: this.viewConfig, getUnsavedAnswerTml }); + } + /** + * Subscribe to message events that depend on successful iframe setup + */ + subscribeToMessageEvents() { + this.unsubscribeToMessageEvents(); + window.addEventListener('message', this.messageEventListener); + this.subscribedListeners.message = this.messageEventListener; + } + /** + * Adds event listeners for both network and message events. + * This maintains backward compatibility with the existing method. + * Adds a global event listener to window for "message" events. + * ThoughtSpot detects if a particular event is targeted to this + * embed instance through an identifier contained in the payload, + * and executes the registered callbacks accordingly. + */ + subscribeToEvents() { + this.subscribeToNetworkEvents(); + this.subscribeToMessageEvents(); + } + unsubscribeToNetworkEvents() { + if (this.subscribedListeners.online) { + window.removeEventListener('online', this.subscribedListeners.online); + delete this.subscribedListeners.online; + } + if (this.subscribedListeners.offline) { + window.removeEventListener('offline', this.subscribedListeners.offline); + delete this.subscribedListeners.offline; + } + } + unsubscribeToMessageEvents() { + if (this.subscribedListeners.message) { + window.removeEventListener('message', this.subscribedListeners.message); + delete this.subscribedListeners.message; + } + } + unsubscribeToEvents() { + Object.keys(this.subscribedListeners).forEach((key) => { + window.removeEventListener(key, this.subscribedListeners[key]); + }); + } + async getAuthTokenForCookielessInit() { + let authToken = ''; + if (this.embedConfig.authType !== AuthType.TrustedAuthTokenCookieless) + return authToken; + try { + authToken = await getAuthenticationToken(this.embedConfig); + } + catch (e) { + processAuthFailure(e, this.isPreRendered ? this.preRenderWrapper : this.hostElement); + throw e; + } + return authToken; + } + async getDefaultAppInitData() { + const authToken = await this.getAuthTokenForCookielessInit(); + const customActionsResult = getCustomActions([ + ...(this.viewConfig.customActions || []), + ...(this.embedConfig.customActions || []) + ]); + if (customActionsResult.errors.length > 0) { + this.handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: customActionsResult.errors, + code: EmbedErrorCodes.CUSTOM_ACTION_VALIDATION, + error: { type: EmbedErrorCodes.CUSTOM_ACTION_VALIDATION, message: customActionsResult.errors } + }); + } + const baseInitData = { + customisations: getCustomisations(this.embedConfig, this.viewConfig), + authToken, + runtimeFilterParams: this.viewConfig.excludeRuntimeFiltersfromURL + ? getRuntimeFilters(this.viewConfig.runtimeFilters) + : null, + runtimeParameterParams: this.viewConfig.excludeRuntimeParametersfromURL + ? getRuntimeParameters(this.viewConfig.runtimeParameters || []) + : null, + hiddenHomepageModules: this.viewConfig.hiddenHomepageModules || [], + reorderedHomepageModules: this.viewConfig.reorderedHomepageModules || [], + hostConfig: this.embedConfig.hostConfig, + hiddenHomeLeftNavItems: this.viewConfig?.hiddenHomeLeftNavItems + ? this.viewConfig?.hiddenHomeLeftNavItems + : [], + customVariablesForThirdPartyTools: this.embedConfig.customVariablesForThirdPartyTools || {}, + hiddenListColumns: this.viewConfig.hiddenListColumns || [], + customActions: customActionsResult.actions, + embedExpiryInAuthToken: this.viewConfig.refreshAuthTokenOnNearExpiry ?? true, + ...getInterceptInitData(this.viewConfig), + ...getHostEventsConfig(this.viewConfig), + }; + return baseInitData; + } + async getAppInitData() { + return this.getDefaultAppInitData(); + } + /** + * Helper method to refresh/update auth token for TrustedAuthTokenCookieless auth type + * @param responder - Function to send response back + * @param eventType - The embed event type to send + * @param forceRefresh - Whether to force refresh the token + * @returns Promise that resolves if token was refreshed, rejects otherwise + */ + async refreshAuthTokenForCookieless(responder, eventType, forceRefresh = false) { + const { authType, autoLogin } = this.embedConfig; + const isAutoLoginTrue = autoLogin ?? (authType === AuthType.TrustedAuthTokenCookieless); + if (isAutoLoginTrue && authType === AuthType.TrustedAuthTokenCookieless) { + const authToken = await getAuthenticationToken(this.embedConfig, forceRefresh); + responder({ + type: eventType, + data: { authToken }, + }); + } + } + /** + * Constructs the base URL string to load the ThoughtSpot app. + * @param query + */ + getEmbedBasePath(query) { + let queryString = query.startsWith('?') ? query : `?${query}`; + if (this.shouldEncodeUrlQueryParams) { + queryString = `?base64UrlEncodedFlags=${getEncodedQueryParamsString(queryString.substr(1))}`; + } + const basePath = [this.thoughtSpotHost, this.thoughtSpotV2Base, queryString] + .filter((x) => x.length > 0) + .join('/'); + return `${basePath}#`; + } + async getUpdateEmbedParamsObject() { + let queryParams = this.getEmbedParamsObject(); + const appInitData = await this.getAppInitData(); + queryParams = { ...this.viewConfig, ...queryParams, ...appInitData }; + return queryParams; + } + /** + * Common query params set for all the embed modes. + * @param queryParams + * @returns queryParams + */ + getBaseQueryParams(queryParams = {}) { + let hostAppUrl = window?.location?.host || ''; + // The below check is needed because TS Cloud firewall, blocks + // localhost/127.0.0.1 in any url param. + if (hostAppUrl.includes('localhost') || hostAppUrl.includes('127.0.0.1')) { + hostAppUrl = 'local-host'; + } + const blockNonEmbedFullAppAccess = this.embedConfig.blockNonEmbedFullAppAccess ?? true; + queryParams[Param.EmbedApp] = true; + queryParams[Param.HostAppUrl] = encodeURIComponent(hostAppUrl); + queryParams[Param.ViewPortHeight] = window.innerHeight; + queryParams[Param.ViewPortWidth] = window.innerWidth; + queryParams[Param.Version] = VERSION; + queryParams[Param.AuthType] = this.embedConfig.authType; + queryParams[Param.blockNonEmbedFullAppAccess] = blockNonEmbedFullAppAccess; + queryParams[Param.AutoLogin] = this.embedConfig.autoLogin; + if (this.embedConfig.disableLoginRedirect === true || this.embedConfig.autoLogin === true) { + queryParams[Param.DisableLoginRedirect] = true; + } + if (this.embedConfig.authType === AuthType.EmbeddedSSO) { + queryParams[Param.ForceSAMLAutoRedirect] = true; + } + if (this.embedConfig.authType === AuthType.TrustedAuthTokenCookieless) { + queryParams[Param.cookieless] = true; + } + if (this.embedConfig.pendoTrackingKey) { + queryParams[Param.PendoTrackingKey] = this.embedConfig.pendoTrackingKey; + } + if (this.embedConfig.numberFormatLocale) { + queryParams[Param.NumberFormatLocale] = this.embedConfig.numberFormatLocale; + } + if (this.embedConfig.dateFormatLocale) { + queryParams[Param.DateFormatLocale] = this.embedConfig.dateFormatLocale; + } + if (this.embedConfig.currencyFormat) { + queryParams[Param.CurrencyFormat] = this.embedConfig.currencyFormat; + } + const { disabledActions, disabledActionReason, hiddenActions, visibleActions, hiddenTabs, visibleTabs, showAlerts, additionalFlags: additionalFlagsFromView, locale, customizations, contextMenuTrigger, linkOverride, enableLinkOverridesV2, insertInToSlide, disableRedirectionLinksInNewTab, overrideOrgId, exposeTranslationIDs, primaryAction, } = this.viewConfig; + const { additionalFlags: additionalFlagsFromInit } = this.embedConfig; + const additionalFlags = { + ...additionalFlagsFromInit, + ...additionalFlagsFromView, + }; + if (Array.isArray(visibleActions) && Array.isArray(hiddenActions)) { + this.handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.CONFLICTING_ACTIONS_CONFIG, + code: EmbedErrorCodes.CONFLICTING_ACTIONS_CONFIG, + error: ERROR_MESSAGE.CONFLICTING_ACTIONS_CONFIG, + }); + return queryParams; + } + if (Array.isArray(visibleTabs) && Array.isArray(hiddenTabs)) { + this.handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.CONFLICTING_TABS_CONFIG, + code: EmbedErrorCodes.CONFLICTING_TABS_CONFIG, + error: ERROR_MESSAGE.CONFLICTING_TABS_CONFIG, + }); + return queryParams; + } + if (primaryAction) { + queryParams[Param.PrimaryAction] = primaryAction; + } + if (disabledActions?.length) { + queryParams[Param.DisableActions] = disabledActions; + } + if (disabledActionReason) { + queryParams[Param.DisableActionReason] = disabledActionReason; + } + if (exposeTranslationIDs) { + queryParams[Param.ExposeTranslationIDs] = exposeTranslationIDs; + } + queryParams[Param.HideActions] = [...this.defaultHiddenActions, ...(hiddenActions ?? [])]; + if (Array.isArray(visibleActions)) { + queryParams[Param.VisibleActions] = visibleActions; + } + if (Array.isArray(hiddenTabs)) { + queryParams[Param.HiddenTabs] = hiddenTabs; + } + if (Array.isArray(visibleTabs)) { + queryParams[Param.VisibleTabs] = visibleTabs; + } + /** + * Default behavior for context menu will be left-click + * from version 9.2.0.cl the user have an option to override context + * menu click + */ + if (contextMenuTrigger === ContextMenuTriggerOptions.LEFT_CLICK) { + queryParams[Param.ContextMenuTrigger] = 'left'; + } + else if (contextMenuTrigger === ContextMenuTriggerOptions.RIGHT_CLICK) { + queryParams[Param.ContextMenuTrigger] = 'right'; + } + else if (contextMenuTrigger === ContextMenuTriggerOptions.BOTH_CLICKS) { + queryParams[Param.ContextMenuTrigger] = 'both'; + } + const embedCustomizations = this.embedConfig.customizations; + const spriteUrl = customizations?.iconSpriteUrl || embedCustomizations?.iconSpriteUrl; + if (spriteUrl) { + queryParams[Param.IconSpriteUrl] = spriteUrl.replace('https://', ''); + } + const stringIDsUrl = customizations?.content?.stringIDsUrl + || embedCustomizations?.content?.stringIDsUrl; + if (stringIDsUrl) { + queryParams[Param.StringIDsUrl] = stringIDsUrl; + } + if (showAlerts !== undefined) { + queryParams[Param.ShowAlerts] = showAlerts; + } + if (locale !== undefined) { + queryParams[Param.Locale] = locale; + } + // TODO: Once V2 is stable, send both flags when + // linkOverride is true (remove the else-if). + if (enableLinkOverridesV2) { + queryParams[Param.EnableLinkOverridesV2] = true; + queryParams[Param.LinkOverride] = true; + } + else if (linkOverride) { + queryParams[Param.LinkOverride] = linkOverride; + } + if (insertInToSlide) { + queryParams[Param.ShowInsertToSlide] = insertInToSlide; + } + if (disableRedirectionLinksInNewTab) { + queryParams[Param.DisableRedirectionLinksInNewTab] = disableRedirectionLinksInNewTab; + } + if (overrideOrgId !== undefined) { + queryParams[Param.OverrideOrgId] = overrideOrgId; + } + if (this.isPreAuthCacheEnabled()) { + queryParams[Param.preAuthCache] = true; + } + queryParams[Param.OverrideNativeConsole] = true; + queryParams[Param.ClientLogLevel] = this.embedConfig.logLevel; + if (isObject_1(additionalFlags) && !isEmpty_1(additionalFlags)) { + Object.assign(queryParams, additionalFlags); + } + // Do not add any flags below this, as we want additional flags to + // override other flags + return queryParams; + } + /** + * Constructs the base URL string to load v1 of the ThoughtSpot app. + * This is used for embedding Liveboards, visualizations, and full application. + * @param queryString The query string to append to the URL. + * @param isAppEmbed A Boolean parameter to specify if you are embedding + * the full application. + */ + getV1EmbedBasePath(queryString) { + const queryParams = this.shouldEncodeUrlQueryParams + ? `?base64UrlEncodedFlags=${getEncodedQueryParamsString(queryString)}` + : `?${queryString}`; + const host = this.thoughtSpotHost; + const path = `${host}/${queryParams}#`; + return path; + } + getEmbedParams() { + const queryParams = this.getEmbedParamsObject(); + return getQueryParamString(queryParams); + } + getEmbedParamsObject() { + const params = this.getBaseQueryParams(); + return params; + } + getRootIframeSrc() { + const query = this.getEmbedParams(); + return this.getEmbedBasePath(query); + } + createIframeEl(frameSrc) { + const iFrame = document.createElement('iframe'); + iFrame.src = frameSrc; + iFrame.id = TS_EMBED_ID; + iFrame.setAttribute('data-ts-iframe', 'true'); + // according to screenfull.js documentation + // allowFullscreen, webkitallowfullscreen and mozallowfullscreen must be + // true + iFrame.allowFullscreen = true; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + iFrame.webkitallowfullscreen = true; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + iFrame.mozallowfullscreen = true; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + iFrame.allow = 'clipboard-read; clipboard-write; fullscreen; local-network-access;'; + const frameParams = this.viewConfig.frameParams; + const { height: frameHeight, width: frameWidth, ...restParams } = frameParams || {}; + const width = getCssDimension(frameWidth || DEFAULT_EMBED_WIDTH); + const height = getCssDimension(frameHeight || DEFAULT_EMBED_HEIGHT); + setAttributes(iFrame, restParams); + iFrame.style.width = `${width}`; + iFrame.style.height = `${height}`; + // Set minimum height to the frame so that, + // scaling down on the fullheight doesn't make it too small. + iFrame.style.minHeight = `${height}`; + iFrame.style.border = '0'; + iFrame.name = 'ThoughtSpot Embedded Analytics'; + return iFrame; + } + /** + * Returns true if this embed instance is configured for pre-rendering. + */ + isPreRenderEmbed() { + return !!this.viewConfig.preRenderId; + } + handleInsertionIntoDOM(child) { + if (this.isPreRenderEmbed()) { + this.insertIntoDOMForPreRender(child); + } + else { + this.insertIntoDOM(child); + } + if (this.insertedDomEl instanceof Node) { + this.insertedDomEl[this.embedNodeKey] = this; + } + if (this.preRenderWrapper) { + this.preRenderWrapper[this.embedNodeKey] = this; + } + } + /** + * Renders the embedded ThoughtSpot app in an iframe and sets up + * event listeners. + * @param url - The URL of the embedded ThoughtSpot app. + */ + async renderIFrame(url) { + if (this.isError) { + return null; + } + if (!this.thoughtSpotHost) { + this.throwInitError(); + } + if (url.length > URL_MAX_LENGTH) ; + return renderInQueue((nextInQueue) => { + const initTimestamp = Date.now(); + this.executeCallbacks(EmbedEvent.Init, { + data: { + timestamp: initTimestamp, + }, + type: EmbedEvent.Init, + }); + uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_RENDER_START); + // Always subscribe to network events, regardless of auth status + this.subscribeToNetworkEvents(); + return getAuthPromise() + ?.then((isLoggedIn) => { + if (!isLoggedIn) { + this.handleInsertionIntoDOM(this.embedConfig.loginFailedMessage); + return; + } + this.setIframeElement(this.iFrame || this.createIframeEl(url)); + this.iFrame.addEventListener('load', () => { + nextInQueue(); + const loadTimestamp = Date.now(); + this.executeCallbacks(EmbedEvent.Load, { + data: { + timestamp: loadTimestamp, + }, + type: EmbedEvent.Load, + }); + uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_RENDER_COMPLETE, { + elWidth: this.iFrame.clientWidth, + elHeight: this.iFrame.clientHeight, + timeTookToLoad: loadTimestamp - initTimestamp, + }); + // Send info event if preauth cache is enabled + if (this.isPreAuthCacheEnabled()) { + getPreauthInfo().then((data) => { + if (data?.info) { + this.trigger(HostEvent.InfoSuccess, data); + } + }); + } + // Setup fullscreen change handler after iframe is + // loaded and ready + this.setupFullscreenChangeHandler(); + }); + this.iFrame.addEventListener('error', () => { + nextInQueue(); + }); + this.handleInsertionIntoDOM(this.iFrame); + const prefetchIframe = document.querySelectorAll('.prefetchIframe'); + if (prefetchIframe.length) { + prefetchIframe.forEach((el) => { + el.remove(); + }); + } + // Subscribe to message events only after successful + // auth and iframe setup + this.subscribeToMessageEvents(); + }) + .catch((error) => { + nextInQueue(); + uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_RENDER_FAILED, { + error: JSON.stringify(error), + }); + this.handleInsertionIntoDOM(this.embedConfig.loginFailedMessage); + this.handleError({ + errorType: ErrorDetailsTypes.API, + message: error.message || ERROR_MESSAGE.LOGIN_FAILED, + code: EmbedErrorCodes.LOGIN_FAILED, + error: error, + }); + }); + }); + } + createPreRenderWrapper() { + const preRenderIds = this.getPreRenderIds(); + document.getElementById(preRenderIds.wrapper)?.remove(); + const preRenderWrapper = document.createElement('div'); + preRenderWrapper.id = preRenderIds.wrapper; + const initialPreRenderWrapperStyle = { + position: 'absolute', + top: '0', + left: '0', + width: '100vw', + height: '100vh', + }; + setStyleProperties(preRenderWrapper, initialPreRenderWrapperStyle); + return preRenderWrapper; + } + /** + * Checks for an existing pre-rendered component and connects to it. + * + * If a matching pre-rendered component is found in the DOM, this method + * sets the internal properties of the embed object to reference it. + * + * @returns True if a connection was successfully established, false otherwise. + */ + connectPreRendered() { + const preRenderIds = this.getPreRenderIds(); + const preRenderWrapperElement = document.getElementById(preRenderIds.wrapper); + this.preRenderWrapper = this.preRenderWrapper || preRenderWrapperElement; + this.preRenderChild = this.preRenderChild || document.getElementById(preRenderIds.child); + if (this.preRenderWrapper && this.preRenderChild) { + this.isPreRendered = true; + if (this.preRenderChild instanceof HTMLIFrameElement) { + this.setIframeElement(this.preRenderChild); + } + this.isRendered = true; + } + return this.isPreRenderConnected(); + } + isPreRenderConnected() { + return (Boolean(this.preRenderWrapper && this.preRenderChild)); + } + createPreRenderChild(child) { + const preRenderIds = this.getPreRenderIds(); + document.getElementById(preRenderIds.child)?.remove(); + if (child instanceof HTMLElement) { + child.id = preRenderIds.child; + return child; + } + const divChildNode = document.createElement('div'); + setStyleProperties(divChildNode, { width: '100%', height: '100%' }); + divChildNode.id = preRenderIds.child; + if (typeof child === 'string') { + divChildNode.innerHTML = child; + } + else { + divChildNode.appendChild(child); + } + return divChildNode; + } + /** + * Creates the in-flow placeholder div inserted into the host element when + * showPreRender() is called. The wrapper observes this element to stay + * aligned with the host layout. + */ + createPreRenderPlaceholder() { + const placeholder = document.createElement('div'); + const id = this.getPreRenderIds(); + const { width: frameWidth, height: frameHeight } = this.viewConfig.frameParams || {}; + const width = getCssDimension(frameWidth || DEFAULT_EMBED_WIDTH); + const height = getCssDimension(frameHeight || DEFAULT_EMBED_HEIGHT); + placeholder.style.width = width; + placeholder.style.height = height; + // we can improve this , lol + placeholder.id = id.placeHolder; + return placeholder; + } + insertIntoDOMForPreRender(child) { + const preRenderChild = this.createPreRenderChild(child); + const preRenderWrapper = this.createPreRenderWrapper(); + preRenderWrapper.appendChild(preRenderChild); + this.preRenderChild = preRenderChild; + this.preRenderWrapper = preRenderWrapper; + if (preRenderChild instanceof HTMLIFrameElement) { + this.setIframeElement(preRenderChild); + } + if (this.iFrame) { + this.iFrame.style.height = '100%'; + this.iFrame.style.width = '100%'; + } + if (this.showPreRenderByDefault) { + this.showPreRender(); + } + else { + this.hidePreRender(); + } + document.body.appendChild(preRenderWrapper); + } + insertIntoDOM(child) { + if (this.viewConfig.insertAsSibling) { + if (typeof child === 'string') { + const div = document.createElement('div'); + div.innerHTML = child; + div.id = TS_EMBED_ID; + child = div; + } + if (this.hostElement.nextElementSibling?.id === TS_EMBED_ID) { + this.hostElement.nextElementSibling.remove(); + } + this.hostElement.parentElement.insertBefore(child, this.hostElement.nextSibling); + this.insertedDomEl = child; + } + else if (typeof child === 'string') { + this.hostElement.innerHTML = child; + this.insertedDomEl = this.hostElement.children[0]; + } + else { + this.hostElement.innerHTML = ''; + this.hostElement.appendChild(child); + this.insertedDomEl = child; + } + } + /** + * Sets the height of the iframe + * @param height The height in pixels + */ + setIFrameHeight(height) { + if (this.isPreRendered) { + if (this.insertedDomEl) + this.insertedDomEl.style.height = getCssDimension(height); + else + this.preRenderWrapper.style.height = getCssDimension(height); + } + else { + // normal (non-preRender) mode: size the iframe directly + this.iFrame.style.height = getCssDimension(height); + } + } + shouldSkipEvent(eventType, data) { + const errorType = data?.errorType ?? data?.data?.code; + if (eventType === EmbedEvent.Error + && errorType === EmbedErrorCodes.HOST_EVENT_VALIDATION + && (!getHostEventsConfig(this.viewConfig).useHostEventsV2 || getHostEventsConfig(this.viewConfig).shouldBypassPayloadValidation)) { + logger$3.warn(`Host Event Validation failed: ${data?.data?.message}`); + return true; + } + return false; + } + /** + * Executes all registered event handlers for a particular event type + * @param eventType The event type + * @param data The payload invoked with the event handler + * @param eventPort The event Port for a specific MessageChannel + */ + executeCallbacks(eventType, data, eventPort) { + if (this.shouldSkipEvent(eventType, data)) + return; + const eventHandlers = this.eventHandlerMap.get(eventType) || []; + const allHandlers = this.eventHandlerMap.get(EmbedEvent.ALL) || []; + const callbacks = [...eventHandlers, ...allHandlers]; + const dataStatus = data?.status || embedEventStatus.END; + callbacks.forEach((callbackObj) => { + if ( + // When start status is true it trigger only start releated + // payload + (callbackObj.options.start && dataStatus === embedEventStatus.START) + // When start status is false it trigger only end releated + // payload + || (!callbackObj.options.start && dataStatus === embedEventStatus.END)) { + const responder = this.createEmbedEventResponder(eventPort, eventType); + callbackObj.callback(data, responder); + } + }); + } + /** + * Returns the ThoughtSpot hostname or IP address. + */ + getThoughtSpotHost() { + return this.thoughtSpotHost; + } + /** + * Gets the v1 event type (if applicable) for the EmbedEvent type + * @param eventType The v2 event type + * @returns The corresponding v1 event type if one exists + * or else the v2 event type itself + */ + getCompatibleEventType(eventType) { + return V1EventMap[eventType] || eventType; + } + /** + * Calculates the iframe center for the current visible viewPort + * of iframe using Scroll position of Host App, offsetTop for iframe + * in Host app. ViewPort height of the tab. + * @returns iframe Center in visible viewport, + * Iframe height, + * View port height. + */ + getIframeCenter() { + const offsetTopClient = getOffsetTop(this.iFrame); + const scrollTopClient = window.scrollY; + const viewPortHeight = window.innerHeight; + const iframeHeight = this.iFrame.offsetHeight; + const iframeScrolled = scrollTopClient - offsetTopClient; + let iframeVisibleViewPort; + let iframeOffset; + if (iframeScrolled < 0) { + iframeVisibleViewPort = viewPortHeight - (offsetTopClient - scrollTopClient); + iframeVisibleViewPort = Math.min(iframeHeight, iframeVisibleViewPort); + iframeOffset = 0; + } + else { + iframeVisibleViewPort = Math.min(iframeHeight - iframeScrolled, viewPortHeight); + iframeOffset = iframeScrolled; + } + const iframeCenter = iframeOffset + iframeVisibleViewPort / 2; + return { + iframeCenter, + iframeScrolled, + iframeHeight, + viewPortHeight, + iframeVisibleViewPort, + }; + } + /** + * Registers an event listener to trigger an alert when the ThoughtSpot app + * sends an event of a particular message type to the host application. + * @param messageType The message type + * @param callback A callback as a function + * @param options The message options + * @param isSelf + * @param isRegisteredBySDK + * @example + * ```js + * tsEmbed.on(EmbedEvent.Error, (data) => { + * console.error(data); + * }); + * ``` + * @example + * ```js + * tsEmbed.on(EmbedEvent.Save, (data) => { + * console.log("Answer save clicked", data); + * }, { + * start: true // This will trigger the callback on start of save + * }); + * ``` + */ + on(messageType, callback, options = { start: false }, isRegisteredBySDK = false) { + uploadMixpanelEvent(`${MIXPANEL_EVENT.VISUAL_SDK_ON}-${messageType}`, { + isRegisteredBySDK, + }); + if (this.isRendered) { + logger$3.warn('Please register event handlers before calling render'); + } + const callbacks = this.eventHandlerMap.get(messageType) || []; + callbacks.push({ options, callback }); + this.eventHandlerMap.set(messageType, callbacks); + return this; + } + /** + * Removes an event listener for a particular event type. + * @param messageType The message type + * @param callback The callback to remove + * @example + * ```js + * const errorHandler = (data) => { console.error(data); }; + * tsEmbed.on(EmbedEvent.Error, errorHandler); + * tsEmbed.off(EmbedEvent.Error, errorHandler); + * ``` + */ + off(messageType, callback) { + const callbacks = this.eventHandlerMap.get(messageType) || []; + const index = callbacks.findIndex((cb) => cb.callback === callback); + if (index > -1) { + callbacks.splice(index, 1); + } + return this; + } + /** + * Triggers an event on specific Port registered against + * for the EmbedEvent + * @param eventType The message type + * @param data The payload to send + * @param eventPort + * @param payload + */ + triggerEventOnPort(eventPort, payload) { + if (eventPort) { + try { + eventPort.postMessage({ + type: payload.type, + data: payload.data, + }); + } + catch (e) { + eventPort.postMessage({ error: e }); + logger$3.log(e); + } + } + else { + logger$3.log('Event Port is not defined'); + } + } + getPreRenderObj() { + const embedObj = this.preRenderWrapper?.[this.embedNodeKey]; + if (embedObj === this) { + logger$3.info('embedObj is same as this'); + } + return embedObj; + } + checkEmbedContainerLoaded() { + if (this.isEmbedContainerLoaded) + return true; + const preRenderObj = this.getPreRenderObj(); + if (preRenderObj && preRenderObj.isEmbedContainerLoaded) { + this.isEmbedContainerLoaded = true; + } + return this.isEmbedContainerLoaded; + } + executeEmbedContainerReadyCallbacks() { + logger$3.debug('executePendingEvents', this.embedContainerReadyCallbacks); + this.embedContainerReadyCallbacks.forEach((callback) => { + callback?.(); + }); + this.embedContainerReadyCallbacks = []; + } + /** + * Executes a callback after the embed container is loaded. + * @param callback The callback to execute + */ + executeAfterEmbedContainerLoaded(callback) { + if (this.checkEmbedContainerLoaded()) { + callback?.(); + } + else { + logger$3.debug('pushing callback to embedContainerReadyCallbacks', callback); + this.embedContainerReadyCallbacks.push(callback); + } + } + /** + * Triggers an event to the embedded app + * @param {HostEvent} messageType The event type + * @param {any} data The payload to send with the message + * @param {ContextType} context Optional context type to specify the context from which the event is triggered. + * Use ContextType.Search for search answer context, ContextType.Answer for answer/explore context, + * ContextType.Liveboard for liveboard context, or ContextType.Spotter for spotter context. + * Available from SDK version 1.45.2 | ThoughtSpot: 26.3.0.cl + * @returns A promise that resolves with the response from the embedded app + * @example + * ```js + * // Trigger Pin event with context (SDK: 1.45.2+) + * import { HostEvent, ContextType } from '@thoughtspot/visual-embed-sdk'; + * embed.trigger(HostEvent.Pin, { + * vizId: "123", + * liveboardId: "456" + * }, ContextType.Search); + * ``` + * @version SDK: 1.45.2 | ThoughtSpot: 26.3.0.cl (for context parameter) + */ + async trigger(messageType, data = {}, context) { + uploadMixpanelEvent(`${MIXPANEL_EVENT.VISUAL_SDK_TRIGGER}-${messageType}`); + if (!this.isRendered) { + this.handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.RENDER_BEFORE_EVENTS_REQUIRED, + code: EmbedErrorCodes.RENDER_NOT_CALLED, + error: ERROR_MESSAGE.RENDER_BEFORE_EVENTS_REQUIRED, + }); + return null; + } + if (!messageType) { + this.handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.HOST_EVENT_TYPE_UNDEFINED, + code: EmbedErrorCodes.HOST_EVENT_TYPE_UNDEFINED, + error: ERROR_MESSAGE.HOST_EVENT_TYPE_UNDEFINED, + }); + return null; + } + // Check if iframe exists before triggering - + // this prevents the error when auth fails + if (!this.iFrame) { + logger$3.debug(`Cannot trigger ${messageType} - iframe not available (likely due to auth failure)`); + return null; + } + // send an empty object, this is needed for liveboard default handlers + return this.hostEventClient.triggerHostEvent(messageType, data, context).catch((err) => { + if (err?.isValidationError) { + const errorDetails = err.embedErrorDetails ?? { + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: err.message || ERROR_MESSAGE.UPDATEFILTERS_INVALID_PAYLOAD, + code: EmbedErrorCodes.UPDATEFILTERS_INVALID_PAYLOAD, + error: err.message, + }; + this.handleError(errorDetails); + } + throw err; + }); + } + /** + * Triggers an event to the embedded app, skipping the UI flow. + * @param {UIPassthroughEvent} apiName - The name of the API to be triggered. + * @param {UIPassthroughRequest} parameters - The parameters to be passed to the API. + * @returns {Promise} - A promise that resolves with the response + * from the embedded app. + */ + async triggerUIPassThrough(apiName, parameters) { + const response = this.hostEventClient.triggerUIPassthroughApi(apiName, parameters); + return response; + } + /** + * Marks the ThoughtSpot object to have been rendered + * Needs to be overridden by subclasses to do the actual + * rendering of the iframe. + * @param args + */ + async render() { + if (!getIsInitCalled()) { + logger$3.error(ERROR_MESSAGE.RENDER_CALLED_BEFORE_INIT); + } + if (this.shouldWaitForRenderPromise) + await this.isReadyForRenderPromise; + this.isRendered = true; + return this; + } + getIframeSrc() { + return ''; + } + handleRenderForPrerender() { + return this.render(); + } + /** + * Context object for the embedded component. + * @returns {ContextObject} The current context object containing the page type and object ids. + * @example + * ```js + * const context = await embed.getCurrentContext(); + * console.log(context); + * + * // Example output + * { + * stack: [ + * { + * name: 'Liveboard', + * type: ContextType.Liveboard, + * objectIds: { + * liveboardId: '123', + * }, + * }, + * ], + * currentContext: { + * name: 'Liveboard', + * type: ContextType.Liveboard, + * objectIds: { + * liveboardId: '123', + * }, + * }, + * } + * ``` + * @version SDK: 1.45.2 | ThoughtSpot: 26.3.0.cl + */ + async getCurrentContext() { + return new Promise((resolve) => { + this.executeAfterEmbedContainerLoaded(async () => { + const context = await this.trigger(HostEvent.GetPageContext, {}); + resolve(context); + }); + }); + } + /** + * Generates the event name for a "Subscribed" embed event. + * + * This helper appends the "Subscribed" suffix to a given host or action event, + * allowing you to listen for subscription lifecycle events in a consistent format. + * + * @param eventName - The host or action event to generate the subscribed event name for. + * @returns The formatted event name (e.g., "Save Subscribed"). + * + * @version SDK: 1.47.2 | ThoughtSpot: 26.3.0.cl + */ + subscribedEvent(eventName) { + return `${eventName} ${EmbedEvent.Subscribed}`; + } + /** + * Creates the preRender shell + * @param showPreRenderByDefault - Show the preRender after render, hidden by default + */ + async preRender(showPreRenderByDefault = false, replaceExistingPreRender = false) { + if (!this.viewConfig.preRenderId) { + logger$3.error(ERROR_MESSAGE.PRERENDER_ID_MISSING); + return this; + } + this.isPreRendered = true; + this.showPreRenderByDefault = showPreRenderByDefault; + const isAlreadyRendered = this.connectPreRendered(); + if (isAlreadyRendered && !replaceExistingPreRender) { + if (this.showPreRenderByDefault) { + this.showPreRender(); + } + return this; + } + return this.handleRenderForPrerender(); + } + /** + * Get the Post Url Params for THOUGHTSPOT from the current + * host app URL. + * THOUGHTSPOT URL params starts with a prefix "ts-" + * @version SDK: 1.14.0 | ThoughtSpot: 8.4.0.cl, 8.4.1-sw + */ + getThoughtSpotPostUrlParams(additionalParams = {}) { + const urlHash = window.location.hash; + const queryParams = window.location.search; + const postHashParams = urlHash.split('?'); + const postURLParams = postHashParams[postHashParams.length - 1]; + const queryParamsObj = new URLSearchParams(queryParams); + const postURLParamsObj = new URLSearchParams(postURLParams); + const params = new URLSearchParams(); + const addKeyValuePairCb = (value, key) => { + if (key.startsWith(THOUGHTSPOT_PARAM_PREFIX)) { + params.append(key, value); + } + }; + queryParamsObj.forEach(addKeyValuePairCb); + postURLParamsObj.forEach(addKeyValuePairCb); + Object.entries(additionalParams).forEach(([k, v]) => params.append(k, v)); + let tsParams = params.toString(); + tsParams = tsParams ? `?${tsParams}` : ''; + return tsParams; + } + /** + * Destroys the ThoughtSpot embed, and remove any nodes from the DOM. + * @version SDK: 1.19.1 | ThoughtSpot: * + */ + destroy() { + try { + this.removeFullscreenChangeHandler(); + this.unsubscribeToEvents(); + this.preRenderWrapper?.remove(); + if (!this.isRendered) { + return; + } + if (!getEmbedConfig().waitForCleanupOnDestroy) { + this.trigger(HostEvent.DestroyEmbed); + this.insertedDomEl?.parentNode?.removeChild(this.insertedDomEl); + } + else { + const cleanupTimeout = getEmbedConfig().cleanupTimeout; + Promise.race([ + this.trigger(HostEvent.DestroyEmbed), + new Promise((resolve) => setTimeout(resolve, cleanupTimeout)), + ]).catch((e) => { + logger$3.log('Error destroying TS Embed', e); + }).finally(() => { + try { + this.insertedDomEl?.parentNode?.removeChild(this.insertedDomEl); + } + catch (e) { + logger$3.log('Error removing DOM element on destroy', e); + } + }); + } + } + catch (e) { + logger$3.log('Error destroying TS Embed', e); + } + } + getUnderlyingFrameElement() { + return this.iFrame; + } + /** + * Prerenders a generic instance of the TS component. + * This means without the path but with the flags already applied. + * This is useful for prerendering the component in the background. + * @version SDK: 1.22.0 + * @returns + */ + async prerenderGeneric() { + if (!getIsInitCalled()) { + logger$3.error(ERROR_MESSAGE.RENDER_CALLED_BEFORE_INIT); + } + if (this.shouldWaitForRenderPromise) + await this.isReadyForRenderPromise; + const prerenderFrameSrc = this.getRootIframeSrc(); + this.isRendered = true; + return this.renderIFrame(prerenderFrameSrc); + } + beforePrerenderVisible() { + // We can ignore this as its a bit expensive and the newer customers + // have moved on to UpdateEmbedParams supported clusters + // this.validatePreRenderViewConfig(this.viewConfig); removed in #517 + logger$3.debug('triggering UpdateEmbedParams', this.viewConfig); + this.executeAfterEmbedContainerLoaded(async () => { + try { + const params = await this.getUpdateEmbedParamsObject(); + this.trigger(HostEvent.UpdateEmbedParams, params); + } + catch (error) { + logger$3.error(ERROR_MESSAGE.UPDATE_PARAMS_FAILED, error); + this.handleError({ + errorType: ErrorDetailsTypes.API, + message: error?.message || ERROR_MESSAGE.UPDATE_PARAMS_FAILED, + code: EmbedErrorCodes.UPDATE_PARAMS_FAILED, + error: error?.message || error, + }); + } + }); + } + /** + * Displays the pre-rendered component inside the host element. + * If the component has not been pre-rendered yet, it initiates rendering first. + * Inserts a placeholder element into the host and positions the pre-render + * wrapper to overlay it. + */ + async showPreRender() { + if (this.shouldWaitForRenderPromise) + await this.isReadyForRenderPromise; + if (!this.viewConfig.preRenderId) { + logger$3.error(ERROR_MESSAGE.PRERENDER_ID_MISSING); + return this; + } + if (!this.isPreRenderConnected()) { + // this will call showPreRender down the line + return this.preRender(true); + } + this.isRendered = true; + this.beforePrerenderVisible(); + if (this.hostElement) { + this.insertedDomEl = this.createPreRenderPlaceholder(); + if (this.viewConfig.fullHeight) { + // If fullHeight has already sized the wrapper, seed the placeholder + // with the same height so syncPreRenderStyle gets an accurate rect. + const existingHeight = this.preRenderWrapper.style.height; + if (existingHeight) { + this.insertedDomEl.style.height = existingHeight; + } + } + const placeHolderId = this.getPreRenderIds().placeHolder; + const oldEle = this.hostElement.querySelector(`#${placeHolderId}`); + if (oldEle) { + this.hostElement.removeChild(oldEle); + } + this.hostElement.appendChild(this.insertedDomEl); + this.syncPreRenderStyle(); + if (!this.viewConfig.doNotTrackPreRenderSize) { + const observeTarget = this.insertedDomEl ?? this.hostElement; + this.resizeObserver = new ResizeObserver((entries) => { + entries.forEach((entry) => { + if (entry.contentRect && entry.target === observeTarget) { + setStyleProperties(this.preRenderWrapper, { + width: `${entry.contentRect.width}px`, + height: `${entry.contentRect.height}px`, + }); + } + }); + }); + this.resizeObserver.observe(observeTarget); + } + } + removeStyleProperties(this.preRenderWrapper, ['z-index', 'opacity', 'pointer-events', 'overflow']); + this.subscribeToEvents(); + // Setup fullscreen change handler for prerendered components + if (this.iFrame) { + this.setupFullscreenChangeHandler(); + } + return this; + } + getPreRenderPlaceHolderElement() { + return this.insertedDomEl; + } + /** + * Synchronizes the style properties of the PreRender component with the embedding + * element. This function adjusts the position, width, and height of the PreRender + * component + * to match the dimensions and position of the embedding element. + * @throws {Error} Throws an error if the embedding element (passed as domSelector) + * is not defined or not found. + */ + syncPreRenderStyle() { + if (!this.isPreRenderConnected() || !this.getPreRenderPlaceHolderElement()) { + logger$3.error(ERROR_MESSAGE.SYNC_STYLE_CALLED_BEFORE_RENDER); + return; + } + const elBoundingClient = this.getPreRenderPlaceHolderElement().getBoundingClientRect(); + setStyleProperties(this.preRenderWrapper, { + top: `${elBoundingClient.y + window.scrollY}px`, + left: `${elBoundingClient.x + window.scrollX}px`, + width: `${elBoundingClient.width}px`, + height: `${elBoundingClient.height}px`, + position: 'absolute' + }); + } + /** + * Hides the PreRender component if it is available. + * If the component is not preRendered, it issues a warning. + */ + hidePreRender() { + logger$3.debug('HidePreRender Called'); + if (!this.isPreRenderConnected()) { + // if the embed component is not preRendered , nothing to hide + logger$3.warn('PreRender should be called before hiding it using hidePreRender.'); + return; + } + const preRenderHideStyles = { + opacity: '0', + pointerEvents: 'none', + zIndex: '-1000', + position: 'absolute', + top: '0', + left: '0', + overflow: 'hidden', + }; + setStyleProperties(this.preRenderWrapper, preRenderHideStyles); + if (this.resizeObserver) { + this.resizeObserver.disconnect(); + } + const placeHolderEle = this.getPreRenderPlaceHolderElement(); + if (placeHolderEle) { + placeHolderEle.parentElement.removeChild(placeHolderEle); + } + this.unsubscribeToEvents(); + } + /** + * Retrieves unique HTML element IDs for PreRender-related elements. + * These IDs are constructed based on the provided 'preRenderId' from 'viewConfig'. + * @returns {object} An object containing the IDs for the PreRender elements. + * @property {string} wrapper - The HTML element ID for the PreRender wrapper. + * @property {string} child - The HTML element ID for the PreRender child. + */ + getPreRenderIds() { + return { + wrapper: `tsEmbed-pre-render-wrapper-${this.viewConfig.preRenderId}`, + child: `tsEmbed-pre-render-child-${this.viewConfig.preRenderId}`, + placeHolder: `tsEmbed-pre-render-placeholder-${this.viewConfig.preRenderId}`, + }; + } + /** + * Returns the answerService which can be used to make arbitrary graphql calls on top + * session. + * @param vizId [Optional] to get for a specific viz in case of a Liveboard. + * @version SDK: 1.25.0 | ThoughtSpot: 9.10.0 + */ + async getAnswerService(vizId) { + const { session } = await this.trigger(HostEvent.GetAnswerSession, vizId ? { vizId } : {}); + return new AnswerService(session, null, this.embedConfig.thoughtSpotHost); + } + /** + * Set up fullscreen change detection to automatically trigger ExitPresentMode + * when user exits fullscreen mode + */ + setupFullscreenChangeHandler() { + const embedConfig = getEmbedConfig(); + const disableFullscreenPresentation = embedConfig?.disableFullscreenPresentation ?? true; + if (disableFullscreenPresentation) { + return; + } + if (this.fullscreenChangeHandler) { + document.removeEventListener('fullscreenchange', this.fullscreenChangeHandler); + } + this.fullscreenChangeHandler = () => { + const isFullscreen = !!document.fullscreenElement; + if (!isFullscreen) { + logger$3.info('Exited fullscreen mode - triggering ExitPresentMode'); + // Only trigger if iframe is available and contentWindow is + // accessible + if (this.iFrame && this.iFrame.contentWindow) { + this.trigger(HostEvent.ExitPresentMode); + } + else { + logger$3.debug('Skipping ExitPresentMode - iframe contentWindow not available'); + } + } + }; + document.addEventListener('fullscreenchange', this.fullscreenChangeHandler); + } + /** + * Remove fullscreen change handler + */ + removeFullscreenChangeHandler() { + if (this.fullscreenChangeHandler) { + document.removeEventListener('fullscreenchange', this.fullscreenChangeHandler); + this.fullscreenChangeHandler = null; + } + } +} +/** + * Base class for embedding v1 experience + * Note: The v1 version of ThoughtSpot Blink works on the AngularJS stack + * which is currently under migration to v2 + * @inheritdoc + */ +class V1Embed extends TsEmbed { + constructor(domSelector, viewConfig) { + super(domSelector, viewConfig); + /** + * Only for testing purposes. + * @hidden + */ + this.test__executeCallbacks = this.executeCallbacks; + this.viewConfig = { + excludeRuntimeFiltersfromURL: true, + excludeRuntimeParametersfromURL: true, + ...viewConfig, + }; + } + /** + * Render the app in an iframe and set up event handlers + * @param iframeSrc + */ + renderV1Embed(iframeSrc) { + return this.renderIFrame(iframeSrc); + } + getRootIframeSrc() { + const queryParams = this.getEmbedParams(); + let queryString = queryParams; + if (!this.viewConfig.excludeRuntimeParametersfromURL) { + const runtimeParameters = this.viewConfig.runtimeParameters; + const parameterQuery = getRuntimeParameters(runtimeParameters || []); + queryString = [parameterQuery, queryParams].filter(Boolean).join('&'); + } + if (!this.viewConfig.excludeRuntimeFiltersfromURL) { + const runtimeFilters = this.viewConfig.runtimeFilters; + const filterQuery = getFilterQuery(runtimeFilters || []); + queryString = [filterQuery, queryString].filter(Boolean).join('&'); + } + return this.viewConfig.enableV2Shell_experimental + ? this.getEmbedBasePath(queryString) + : this.getV1EmbedBasePath(queryString); + } + /** + * @inheritdoc + * @example + * ```js + * tsEmbed.on(EmbedEvent.Error, (data) => { + * console.error(data); + * }); + * ``` + * @example + * ```js + * tsEmbed.on(EmbedEvent.Save, (data) => { + * console.log("Answer save clicked", data); + * }, { + * start: true // This will trigger the callback on start of save + * }); + * ``` + */ + on(messageType, callback, options = { start: false }) { + const eventType = this.getCompatibleEventType(messageType); + return super.on(eventType, callback, options); + } +} + +/** + * Resolves enablePastConversationsSidebar with + * spotterSidebarConfig taking precedence over the + * standalone flag. + */ +const resolveEnablePastConversationsSidebar = (params) => (params.spotterSidebarConfigValue !== undefined + ? params.spotterSidebarConfigValue + : params.standaloneValue); +function buildSpotterSidebarAppInitData(defaultAppInitData, viewConfig, handleError) { + const { spotterSidebarConfig, enablePastConversationsSidebar, visualOverrides } = viewConfig; + const resolvedEnablePastConversations = resolveEnablePastConversationsSidebar({ + spotterSidebarConfigValue: spotterSidebarConfig?.enablePastConversationsSidebar, + standaloneValue: enablePastConversationsSidebar, + }); + const hasConfig = spotterSidebarConfig || resolvedEnablePastConversations !== undefined; + if (!hasConfig) { + if (visualOverrides === undefined) { + return defaultAppInitData; + } + return { + ...defaultAppInitData, + embedParams: { visualOverridesParams: visualOverrides }, + }; + } + const resolvedSidebarConfig = { + ...spotterSidebarConfig, + ...(resolvedEnablePastConversations !== undefined && { + enablePastConversationsSidebar: resolvedEnablePastConversations, + }), + }; + if (resolvedSidebarConfig.spotterDocumentationUrl !== undefined) { + const [isValid, validationError] = validateHttpUrl(resolvedSidebarConfig.spotterDocumentationUrl); + if (!isValid) { + handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.INVALID_SPOTTER_DOCUMENTATION_URL, + code: EmbedErrorCodes.INVALID_URL, + error: validationError?.message || ERROR_MESSAGE.INVALID_SPOTTER_DOCUMENTATION_URL, + }); + delete resolvedSidebarConfig.spotterDocumentationUrl; + } + } + return { + ...defaultAppInitData, + embedParams: { + ...(defaultAppInitData.embedParams || {}), + spotterSidebarConfig: resolvedSidebarConfig, + ...(visualOverrides !== undefined ? { visualOverridesParams: visualOverrides } : {}), + }, + }; +} + +function buildSpotterVizAppInitData(initData, viewConfig) { + const { spotterViz } = viewConfig; + if (!spotterViz) + return initData; + return { + ...initData, + embedParams: { + ...(initData.embedParams || {}), + spotterVizConfig: spotterViz, + }, + }; +} + +/** + * Copyright (c) 2022 + * + * Full application embedding + * https://developers.thoughtspot.com/docs/?pageid=full-embed + * @summary Full app embed + * @module + * @author Ayon Ghosh + */ +/** + * Pages within the ThoughtSpot app that can be embedded. + */ +var Page; +(function (Page) { + /** + * Home page + */ + Page["Home"] = "home"; + /** + * Search page + */ + Page["Search"] = "search"; + /** + * Saved answers listing page + */ + Page["Answers"] = "answers"; + /** + * Liveboards listing page + */ + Page["Liveboards"] = "liveboards"; + /** + * @hidden + */ + Page["Pinboards"] = "pinboards"; + /** + * Data management page + */ + Page["Data"] = "data"; + /** + * SpotIQ listing page + */ + Page["SpotIQ"] = "insights"; + /** + * Monitor Alerts Page + */ + Page["Monitor"] = "monitor"; +})(Page || (Page = {})); +/** + * Define the initial state of column custom group accordions + * in data panel v2. + */ +var DataPanelCustomColumnGroupsAccordionState$1; +(function (DataPanelCustomColumnGroupsAccordionState) { + /** + * Expand all the accordion initially in data panel v2. + */ + DataPanelCustomColumnGroupsAccordionState["EXPAND_ALL"] = "EXPAND_ALL"; + /** + * Collapse all the accordions initially in data panel v2. + */ + DataPanelCustomColumnGroupsAccordionState["COLLAPSE_ALL"] = "COLLAPSE_ALL"; + /** + * Expand the first accordion and collapse the rest. + */ + DataPanelCustomColumnGroupsAccordionState["EXPAND_FIRST"] = "EXPAND_FIRST"; +})(DataPanelCustomColumnGroupsAccordionState$1 || (DataPanelCustomColumnGroupsAccordionState$1 = {})); +var HomePageSearchBarMode; +(function (HomePageSearchBarMode) { + HomePageSearchBarMode["OBJECT_SEARCH"] = "objectSearch"; + HomePageSearchBarMode["AI_ANSWER"] = "aiAnswer"; + HomePageSearchBarMode["NONE"] = "none"; +})(HomePageSearchBarMode || (HomePageSearchBarMode = {})); +/** + * Define the version of the primary navbar + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ +var PrimaryNavbarVersion; +(function (PrimaryNavbarVersion) { + /** + * Sliding (v3) introduces a new left-side navigation hub featuring a tab switcher, + * along with updates to the top navigation bar. + * It serves as the foundational version of the PrimaryNavBar. + */ + PrimaryNavbarVersion["Sliding"] = "v3"; +})(PrimaryNavbarVersion || (PrimaryNavbarVersion = {})); +/** + * Define the version of the home page + * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl + */ +var HomePage; +(function (HomePage) { + /** + * Modular (v2) introduces the updated Modular Home Experience. + * It serves as the foundational version of the home page. + */ + HomePage["Modular"] = "v2"; + /** + * ModularWithStylingChanges (v3) introduces Modular Home Experience + * with styling changes. + */ + HomePage["ModularWithStylingChanges"] = "v3"; + /** + * Focused (v4) introduces the V4 homepage experience + * in which Watchlist and recents and incorporated together + * to form a more focused homepage. + * Pre-requisite : spotter enablement + * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl + */ + HomePage["Focused"] = "v4"; +})(HomePage || (HomePage = {})); +/** + * Define the version of the list page + * @version SDK: 1.40.0 | ThoughtSpot: 10.12.0.cl + */ +var ListPage; +(function (ListPage) { + /** + * List (v2) is the traditional List Experience. + * It serves as the foundational version of the list page. + */ + ListPage["List"] = "v2"; + /** + * ListWithUXChanges (v3) introduces the new updated list page with UX changes. + */ + ListPage["ListWithUXChanges"] = "v3"; +})(ListPage || (ListPage = {})); +/** + * Embeds full ThoughtSpot experience in a host application. + * @group Embed components + */ +class AppEmbed extends V1Embed { + constructor(domSelector, viewConfig) { + viewConfig.embedComponentType = 'AppEmbed'; + super(domSelector, viewConfig); + this.defaultHeight = 500; + this.sendFullHeightLazyLoadData = () => { + const data = calculateVisibleElementData(this.iFrame); + // this should be fired only if the lazyLoadingForFullHeight and fullHeight are true + if (this.viewConfig.lazyLoadingForFullHeight && this.viewConfig.fullHeight) { + this.trigger(HostEvent.VisibleEmbedCoordinates, data); + } + }; + /** + * This is a handler for the RequestVisibleEmbedCoordinates event. + * It is used to send the visible coordinates data to the host application. + * @param data The event payload + * @param responder The responder function + */ + this.requestVisibleEmbedCoordinatesHandler = (data, responder) => { + logger$3.info('Sending RequestVisibleEmbedCoordinates', data); + const visibleCoordinatesData = calculateVisibleElementData(this.iFrame); + responder({ type: EmbedEvent.RequestVisibleEmbedCoordinates, data: visibleCoordinatesData }); + }; + /** + * Set the iframe height as per the computed height received + * from the ThoughtSpot app. + * @param data The event payload + */ + this.updateIFrameHeight = (data) => { + this.setIFrameHeight(Math.max(data.data, this.defaultHeight)); + this.sendFullHeightLazyLoadData(); + }; + this.embedIframeCenter = (data, responder) => { + const obj = this.getIframeCenter(); + responder({ type: EmbedEvent.EmbedIframeCenter, data: obj }); + }; + this.setIframeHeightForNonEmbedLiveboard = (data) => { + const { height: frameHeight } = this.viewConfig.frameParams || {}; + const liveboardRelatedRoutes = [ + '/pinboard/', + '/insights/pinboard/', + '/schedules/', + '/embed/viz/', + '/embed/insights/viz/', + '/liveboard/', + '/insights/liveboard/', + '/tsl-editor/PINBOARD_ANSWER_BOOK/', + '/import-tsl/PINBOARD_ANSWER_BOOK/', + ]; + if (liveboardRelatedRoutes.some((path) => data.data.currentPath.startsWith(path))) { + // Ignore the height reset of the frame, if the navigation is + // only within the liveboard page. + return; + } + this.setIFrameHeight(frameHeight || this.defaultHeight); + }; + if (this.viewConfig.fullHeight === true) { + this.on(EmbedEvent.RouteChange, this.setIframeHeightForNonEmbedLiveboard); + this.on(EmbedEvent.EmbedHeight, this.updateIFrameHeight); + this.on(EmbedEvent.EmbedIframeCenter, this.embedIframeCenter); + this.on(EmbedEvent.RequestVisibleEmbedCoordinates, this.requestVisibleEmbedCoordinatesHandler); + } + } + /** + * Extends the default APP_INIT payload with `embedParams.spotterSidebarConfig` + * so the conv-assist app can read sidebar configuration on initialisation. + * + * Precedence for `enablePastConversationsSidebar`: + * `spotterSidebarConfig.enablePastConversationsSidebar` wins over the + * deprecated top-level `enablePastConversationsSidebar` flag; if the former + * is absent the latter is used as a fallback. + * + * An invalid `spotterDocumentationUrl` triggers a validation error and is + * excluded from the payload rather than forwarded to the app. + */ + async getAppInitData() { + const defaultAppInitData = await super.getAppInitData(); + const sidebarInitData = buildSpotterSidebarAppInitData(defaultAppInitData, this.viewConfig, this.handleError.bind(this)); + return buildSpotterVizAppInitData(sidebarInitData, this.viewConfig); + } + /** + * Constructs a map of parameters to be passed on to the + * embedded Liveboard or visualization. + */ + getEmbedParams() { + const { tag, hideTagFilterChips, hideObjects, liveboardV2, showPrimaryNavbar, disableProfileAndHelp, hideHamburger, hideObjectSearch, hideNotification, hideApplicationSwitcher, hideOrgSwitcher, enableSearchAssist, newConnectionsExperience, fullHeight, dataPanelV2 = true, hideLiveboardHeader = false, showLiveboardTitle = true, showLiveboardDescription = true, showMaskedFilterChip = false, isLiveboardMasterpiecesEnabled = false, newChartsLibrary, hideHomepageLeftNav = false, modularHomeExperience = false, isLiveboardHeaderSticky = true, enableAskSage, collapseSearchBarInitially = false, enable2ColumnLayout, enableCustomColumnGroups = false, dataPanelCustomGroupsAccordionInitialState = DataPanelCustomColumnGroupsAccordionState$1.EXPAND_ALL, collapseSearchBar = true, isLiveboardCompactHeaderEnabled = false, showLiveboardVerifiedBadge = true, showLiveboardReverifyBanner = true, hideIrrelevantChipsInLiveboardTabs = false, isEnhancedFilterInteractivityEnabled = false, homePageSearchBarMode, isUnifiedSearchExperienceEnabled = true, enablePendoHelp = true, discoveryExperience, coverAndFilterOptionInPDF = false, isLiveboardStylingAndGroupingEnabled, isPNGInScheduledEmailsEnabled = false, isLiveboardXLSXCSVDownloadEnabled = false, isGranularXLSXCSVSchedulesEnabled = false, isCentralizedLiveboardFilterUXEnabled = false, isLinkParametersEnabled, updatedSpotterChatPrompt, enableStopAnswerGenerationEmbed, spotterChatConfig, minimumHeight, isThisPeriodInDateFiltersEnabled, enableHomepageAnnouncement = false, isContinuousLiveboardPDFEnabled = false, enableLiveboardDataCache, } = this.viewConfig; + let params = {}; + params[Param.PrimaryNavHidden] = !showPrimaryNavbar; + params[Param.HideProfleAndHelp] = !!disableProfileAndHelp; + params[Param.HideApplicationSwitcher] = !!hideApplicationSwitcher; + params[Param.HideOrgSwitcher] = !!hideOrgSwitcher; + params[Param.HideLiveboardHeader] = hideLiveboardHeader; + params[Param.ShowLiveboardTitle] = showLiveboardTitle; + params[Param.ShowLiveboardDescription] = !!showLiveboardDescription; + params[Param.ShowMaskedFilterChip] = showMaskedFilterChip; + params[Param.IsLiveboardMasterpiecesEnabled] = isLiveboardMasterpiecesEnabled; + if (newChartsLibrary !== undefined) { + params[Param.EnableNewChartLibrary] = newChartsLibrary; + } + params[Param.LiveboardHeaderSticky] = isLiveboardHeaderSticky; + params[Param.IsFullAppEmbed] = true; + params[Param.LiveboardHeaderV2] = isLiveboardCompactHeaderEnabled; + params[Param.IsEnhancedFilterInteractivityEnabled] = isEnhancedFilterInteractivityEnabled; + params[Param.ShowLiveboardVerifiedBadge] = showLiveboardVerifiedBadge; + params[Param.ShowLiveboardReverifyBanner] = showLiveboardReverifyBanner; + params[Param.HideIrrelevantFiltersInTab] = hideIrrelevantChipsInLiveboardTabs; + if (isUnifiedSearchExperienceEnabled !== undefined) { + params[Param.IsUnifiedSearchExperienceEnabled] = isUnifiedSearchExperienceEnabled; + } + params[Param.CoverAndFilterOptionInPDF] = !!coverAndFilterOptionInPDF; + params = this.getBaseQueryParams(params); + if (!isUndefined(updatedSpotterChatPrompt)) { + params[Param.UpdatedSpotterChatPrompt] = !!updatedSpotterChatPrompt; + } + if (!isUndefined(enableStopAnswerGenerationEmbed)) { + params[Param.EnableStopAnswerGenerationEmbed] = !!enableStopAnswerGenerationEmbed; + } + // Handle spotterChatConfig params + if (spotterChatConfig) { + const { hideToolResponseCardBranding, toolResponseCardBrandingLabel, spotterFileUploadEnabled, spotterFileUploadFileTypes, } = spotterChatConfig; + setParamIfDefined(params, Param.HideToolResponseCardBranding, hideToolResponseCardBranding, true); + setParamIfDefined(params, Param.ToolResponseCardBrandingLabel, toolResponseCardBrandingLabel); + if (spotterFileUploadEnabled !== undefined) { + params[Param.SpotterFileUploadEnabled] = spotterFileUploadEnabled; + } + if (spotterFileUploadFileTypes !== undefined) { + params[Param.SpotterFileUploadFileTypes] = JSON.stringify(spotterFileUploadFileTypes); + } + } + if (hideObjectSearch) { + params[Param.HideObjectSearch] = !!hideObjectSearch; + } + if (hideHamburger) { + params[Param.HideHamburger] = !!hideHamburger; + } + if (hideNotification) { + params[Param.HideNotification] = !!hideNotification; + } + if (fullHeight === true) { + params[Param.fullHeight] = true; + if (this.viewConfig.lazyLoadingForFullHeight) { + params[Param.IsLazyLoadingForEmbedEnabled] = true; + if (isValidCssMargin(this.viewConfig.lazyLoadingMargin)) { + params[Param.RootMarginForLazyLoad] = this.viewConfig.lazyLoadingMargin; + } + } + } + if (tag) { + params[Param.Tag] = tag; + } + if (hideObjects && hideObjects.length) { + params[Param.HideObjects] = JSON.stringify(hideObjects); + } + if (liveboardV2 !== undefined) { + params[Param.LiveboardV2Enabled] = liveboardV2; + } + if (enableSearchAssist !== undefined) { + params[Param.EnableSearchAssist] = enableSearchAssist; + } + if (newConnectionsExperience !== undefined) { + params[Param.EnableConnectionNewExperience] = newConnectionsExperience; + } + if (enable2ColumnLayout !== undefined) { + params[Param.Enable2ColumnLayout] = enable2ColumnLayout; + } + if (enableAskSage) { + params[Param.enableAskSage] = enableAskSage; + } + if (homePageSearchBarMode) { + params[Param.HomePageSearchBarMode] = homePageSearchBarMode; + } + if (enablePendoHelp !== undefined) { + params[Param.EnablePendoHelp] = enablePendoHelp; + } + if (isLiveboardStylingAndGroupingEnabled !== undefined) { + params[Param.IsLiveboardStylingAndGroupingEnabled] = isLiveboardStylingAndGroupingEnabled; + } + if (isPNGInScheduledEmailsEnabled !== undefined) { + params[Param.isPNGInScheduledEmailsEnabled] = isPNGInScheduledEmailsEnabled; + } + if (isLiveboardXLSXCSVDownloadEnabled !== undefined) { + params[Param.isLiveboardXLSXCSVDownloadEnabled] = isLiveboardXLSXCSVDownloadEnabled; + } + if (isGranularXLSXCSVSchedulesEnabled !== undefined) { + params[Param.isGranularXLSXCSVSchedulesEnabled] = isGranularXLSXCSVSchedulesEnabled; + } + if (hideTagFilterChips !== undefined) { + params[Param.HideTagFilterChips] = hideTagFilterChips; + } + if (isLinkParametersEnabled !== undefined) { + params[Param.isLinkParametersEnabled] = isLinkParametersEnabled; + } + if (isCentralizedLiveboardFilterUXEnabled != undefined) { + params[Param.isCentralizedLiveboardFilterUXEnabled] = isCentralizedLiveboardFilterUXEnabled; + } + if (isThisPeriodInDateFiltersEnabled !== undefined) { + params[Param.IsThisPeriodInDateFiltersEnabled] = isThisPeriodInDateFiltersEnabled; + } + if (enableHomepageAnnouncement !== undefined) { + params[Param.EnableHomepageAnnouncement] = enableHomepageAnnouncement; + } + if (isContinuousLiveboardPDFEnabled !== undefined) { + params[Param.IsWYSIWYGLiveboardPDFEnabled] = isContinuousLiveboardPDFEnabled; + } + this.defaultHeight = minimumHeight || this.defaultHeight; + if (enableLiveboardDataCache !== undefined) { + params[Param.EnableLiveboardDataCache] = enableLiveboardDataCache; + } + params[Param.DataPanelV2Enabled] = dataPanelV2; + params[Param.HideHomepageLeftNav] = hideHomepageLeftNav; + params[Param.ModularHomeExperienceEnabled] = modularHomeExperience; + params[Param.CollapseSearchBarInitially] = collapseSearchBarInitially || collapseSearchBar; + params[Param.EnableCustomColumnGroups] = enableCustomColumnGroups; + if (dataPanelCustomGroupsAccordionInitialState + === DataPanelCustomColumnGroupsAccordionState$1.COLLAPSE_ALL + || dataPanelCustomGroupsAccordionInitialState + === DataPanelCustomColumnGroupsAccordionState$1.EXPAND_FIRST) { + params[Param.DataPanelCustomGroupsAccordionInitialState] = dataPanelCustomGroupsAccordionInitialState; + } + else { + params[Param.DataPanelCustomGroupsAccordionInitialState] = DataPanelCustomColumnGroupsAccordionState$1.EXPAND_ALL; + } + if (modularHomeExperience !== undefined) { + params[Param.ModularHomeExperienceEnabled] = modularHomeExperience; + } + // Set navigation to v2 by default to avoid problems like the app + // switcher (9-dot menu) not showing when v3 navigation is turned on + // at the cluster level. + // To use v3 navigation, we must manually set the discoveryExperience + // settings. + params[Param.NavigationVersion] = 'v2'; + // Set homePageVersion to v2 by default to reset the LD flag value + // for the homepageVersion. + params[Param.HomepageVersion] = 'v2'; + if (discoveryExperience) { + // primaryNavbarVersion v3 will enabled the new left navigation + if (discoveryExperience.primaryNavbarVersion === PrimaryNavbarVersion.Sliding) { + params[Param.NavigationVersion] = discoveryExperience.primaryNavbarVersion; + // Enable the modularHomeExperience when Sliding is enabled. + params[Param.ModularHomeExperienceEnabled] = true; + } + // homePage v2 will enable the modular home page + // and it will override the modularHomeExperience value + if (discoveryExperience.homePage === HomePage.Modular) { + params[Param.ModularHomeExperienceEnabled] = true; + } + // ModularWithStylingChanges (v3) introduces the styling changes + // to the Modular Homepage. + // v3 will be the base version of homePageVersion. + if (discoveryExperience.homePage === HomePage.ModularWithStylingChanges) { + params[Param.HomepageVersion] = HomePage.ModularWithStylingChanges; + } + // listPageVersion can be changed to v2 or v3 + if (discoveryExperience.listPageVersion !== undefined) { + params[Param.ListPageVersion] = discoveryExperience.listPageVersion; + } + if (discoveryExperience.homePage === HomePage.Focused) { + params[Param.HomepageVersion] = HomePage.Focused; + } + } + const queryParams = getQueryParamString(params, true); + return queryParams; + } + /** + * Constructs the URL of the ThoughtSpot app page to be rendered. + * @param pageId The ID of the page to be embedded. + */ + getIFrameSrc() { + const { pageId, path, modularHomeExperience } = this.viewConfig; + const pageRoute = this.formatPath(path) || this.getPageRoute(pageId, modularHomeExperience); + let url = `${this.getRootIframeSrc()}/${pageRoute}`; + const tsPostHashParams = this.getThoughtSpotPostUrlParams(); + url = `${url}${tsPostHashParams}`; + return url; + } + /** + * Gets the ThoughtSpot route of the page for a particular page ID. + * @param pageId The identifier for a page in the ThoughtSpot app. + * @param modularHomeExperience + */ + getPageRoute(pageId, modularHomeExperience = false) { + switch (pageId) { + case Page.Search: + return 'answer'; + case Page.Answers: + return modularHomeExperience ? 'home/answers' : 'answers'; + case Page.Liveboards: + return modularHomeExperience ? 'home/liveboards' : 'pinboards'; + case Page.Pinboards: + return modularHomeExperience ? 'home/liveboards' : 'pinboards'; + case Page.Data: + return 'data/tables'; + case Page.SpotIQ: + return modularHomeExperience ? 'home/spotiq-analysis' : 'insights/results'; + case Page.Monitor: + return modularHomeExperience ? 'home/monitor-alerts' : 'insights/monitor-alerts'; + case Page.Home: + default: + return 'home'; + } + } + /** + * Formats the path provided by the user. + * @param path The URL path. + * @returns The URL path that the embedded app understands. + */ + formatPath(path) { + if (!path) { + return null; + } + // remove leading slash + if (path.indexOf('/') === 0) { + return path.substring(1); + } + return path; + } + /** + * Navigate to particular page for app embed. eg:answers/pinboards/home + * This is used for embedding answers, pinboards, visualizations and full application + * only. + * @param path string | number The string, set to iframe src and navigate to new page + * eg: appEmbed.navigateToPage('pinboards') + * When used with `noReload` (default: true) this can also be a number + * like 1/-1 to go forward/back. + * @param noReload boolean Trigger the navigation without reloading the page + * @version SDK: 1.12.0 | ThoughtSpot: 8.4.0.cl, 8.4.1-sw + */ + navigateToPage(path, noReload = false) { + if (!this.iFrame) { + logger$3.log('Please call render before invoking this method'); + return; + } + if (noReload) { + this.trigger(HostEvent.Navigate, path); + } + else { + if (typeof path !== 'string') { + logger$3.warn('Path can only by a string when triggered without noReload'); + return; + } + const iframeSrc = this.iFrame.src; + const embedPath = '#/embed'; + const currentPath = iframeSrc.includes(embedPath) ? embedPath : '#'; + this.iFrame.src = `${iframeSrc.split(currentPath)[0]}${currentPath}/${path.replace(/^\/?#?\//, '')}`; + } + } + /** + * Destroys the ThoughtSpot embed, and remove any nodes from the DOM. + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + destroy() { + super.destroy(); + this.unregisterLazyLoadEvents(); + } + postRender() { + this.registerLazyLoadEvents(); + } + registerLazyLoadEvents() { + if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) { + // TODO: Use passive: true, install modernizr to check for passive + window.addEventListener('resize', this.sendFullHeightLazyLoadData); + window.addEventListener('scroll', this.sendFullHeightLazyLoadData, true); + } + } + unregisterLazyLoadEvents() { + if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) { + window.removeEventListener('resize', this.sendFullHeightLazyLoadData); + window.removeEventListener('scroll', this.sendFullHeightLazyLoadData); + } + } + /** + * Renders the embedded application pages in the ThoughtSpot app. + * @param renderOptions An object containing the page ID + * to be embedded. + */ + async render() { + await super.render(); + const src = this.getIFrameSrc(); + await this.renderV1Embed(src); + this.postRender(); + return this; + } +} + +const getPreviewQuery = ` +query GetEurekaVizSnapshots( + $vizId: String!, $liveboardId: String!) { + getEurekaVizSnapshot( + id: $vizId + reportBookId: $liveboardId + reportBookType: "PINBOARD_ANSWER_BOOK" + version: 9999999 + ) { + id + vizContent + snapshotType + createdMs + } + } +`; +/** + * + * @param thoughtSpotHost + * @param vizId + * @param liveboardId + */ +async function getPreview(thoughtSpotHost, vizId, liveboardId) { + return graphqlQuery({ + query: getPreviewQuery, + variables: { vizId, liveboardId }, + thoughtSpotHost, + }); +} + +const addPreviewStylesIfNotPresent = () => { + const styleEl = document.getElementById('ts-preview-style'); + if (styleEl) { + return; + } + const previewStyles = ` + + + `; + document.head.insertAdjacentHTML('beforeend', previewStyles); +}; + +/** + * Copyright (c) 2022 + * + * Embed a ThoughtSpot Liveboard or visualization + * https://developers.thoughtspot.com/docs/embed-liveboard + * https://developers.thoughtspot.com/docs/embed-a-viz + * @summary Liveboard & visualization embed + * @author Ayon Ghosh + */ +/** + * Embed a ThoughtSpot Liveboard or visualization. When rendered it already + * waits for the authentication to complete, so you need not wait for + * `AuthStatus.SUCCESS`. + * @group Embed components + * @example + * ```js + * import { .. } from '@thoughtspot/visual-embed-sdk'; + * init({ ... }); + * const embed = new LiveboardEmbed("#container", { + * liveboardId: , + * // .. other params here. + * }) + * ``` + */ +class LiveboardEmbed extends V1Embed { + constructor(domSelector, viewConfig) { + viewConfig.embedComponentType = 'LiveboardEmbed'; + super(domSelector, viewConfig); + this.defaultHeight = 500; + this.sendFullHeightLazyLoadData = () => { + const data = calculateVisibleElementData(this.iFrame); + // this should be fired only if the lazyLoadingForFullHeight and fullHeight are true + if (this.viewConfig.lazyLoadingForFullHeight && this.viewConfig.fullHeight) { + this.trigger(HostEvent.VisibleEmbedCoordinates, data); + } + }; + /** + * This is a handler for the RequestVisibleEmbedCoordinates event. + * It is used to send the visible coordinates data to the host application. + * @param data The event payload + * @param responder The responder function + */ + this.requestVisibleEmbedCoordinatesHandler = (data, responder) => { + logger$3.info('Sending RequestVisibleEmbedCoordinates', data); + const visibleCoordinatesData = calculateVisibleElementData(this.iFrame); + responder({ type: EmbedEvent.RequestVisibleEmbedCoordinates, data: visibleCoordinatesData }); + }; + /** + * Set the iframe height as per the computed height received + * from the ThoughtSpot app. + * @param data The event payload + */ + this.updateIFrameHeight = (data) => { + this.setIFrameHeight(Math.max(data.data, this.defaultHeight)); + this.sendFullHeightLazyLoadData(); + }; + this.embedIframeCenter = (data, responder) => { + const obj = this.getIframeCenter(); + responder({ type: EmbedEvent.EmbedIframeCenter, data: obj }); + }; + this.setIframeHeightForNonEmbedLiveboard = (data) => { + const { height: frameHeight } = this.viewConfig.frameParams || {}; + const liveboardRelatedRoutes = [ + '/pinboard/', + '/insights/pinboard/', + '/schedules/', + '/embed/viz/', + '/embed/insights/viz/', + '/liveboard/', + '/insights/liveboard/', + '/tsl-editor/PINBOARD_ANSWER_BOOK/', + '/import-tsl/PINBOARD_ANSWER_BOOK/', + ]; + if (liveboardRelatedRoutes.some((path) => data.data.currentPath.startsWith(path))) { + // Ignore the height reset of the frame, if the navigation is + // only within the liveboard page. + return; + } + this.setIFrameHeight(frameHeight || this.defaultHeight); + }; + /** + * @hidden + * Internal state to track the current liveboard id. + * This is used to navigate to the correct liveboard when the prerender is visible. + */ + this.currentLiveboardState = { + liveboardId: this.viewConfig.liveboardId, + vizId: this.viewConfig.vizId, + activeTabId: this.viewConfig.activeTabId, + personalizedViewId: this.viewConfig.personalizedViewId, + }; + if (this.viewConfig.fullHeight === true) { + if (this.viewConfig.vizId) { + logger$3.warn('Full height is currently only supported for Liveboard embeds.' + + 'Using full height with vizId might lead to unexpected behavior.'); + } + this.on(EmbedEvent.RouteChange, this.setIframeHeightForNonEmbedLiveboard); + this.on(EmbedEvent.EmbedHeight, this.updateIFrameHeight); + this.on(EmbedEvent.EmbedIframeCenter, this.embedIframeCenter); + this.on(EmbedEvent.RequestVisibleEmbedCoordinates, this.requestVisibleEmbedCoordinatesHandler); + } + } + async getAppInitData() { + const defaultAppInitData = await super.getAppInitData(); + return buildSpotterVizAppInitData(defaultAppInitData, this.viewConfig); + } + /** + * Construct a map of params to be passed on to the + * embedded Liveboard or visualization. + */ + getEmbedParams() { + const params = this.getEmbedParamsObject(); + const queryParams = getQueryParamString(params, true); + return queryParams; + } + getEmbedParamsObject() { + let params = {}; + params = this.getBaseQueryParams(params); + const { enableVizTransformations, fullHeight, defaultHeight, minimumHeight, visibleVizs, liveboardV2, vizId, hideTabPanel, activeTabId, hideLiveboardHeader, showLiveboardDescription, showLiveboardTitle, isLiveboardHeaderSticky = true, isLiveboardCompactHeaderEnabled = false, showLiveboardVerifiedBadge = true, showLiveboardReverifyBanner = true, hideIrrelevantChipsInLiveboardTabs = false, showMaskedFilterChip = false, isLiveboardMasterpiecesEnabled = false, newChartsLibrary, isEnhancedFilterInteractivityEnabled = false, enableAskSage, enable2ColumnLayout, dataPanelV2 = true, enableCustomColumnGroups = false, oAuthPollingInterval, isForceRedirect, dataSourceId, coverAndFilterOptionInPDF = false, isLiveboardStylingAndGroupingEnabled, isPNGInScheduledEmailsEnabled = false, isLiveboardXLSXCSVDownloadEnabled = false, isGranularXLSXCSVSchedulesEnabled = false, showSpotterLimitations, isCentralizedLiveboardFilterUXEnabled = false, isLinkParametersEnabled, updatedSpotterChatPrompt, enableStopAnswerGenerationEmbed, spotterChatConfig, isThisPeriodInDateFiltersEnabled, isContinuousLiveboardPDFEnabled = false, enableLiveboardDataCache, } = this.viewConfig; + const preventLiveboardFilterRemoval = this.viewConfig.preventLiveboardFilterRemoval + || this.viewConfig.preventPinboardFilterRemoval; + if (fullHeight === true) { + params[Param.fullHeight] = true; + if (this.viewConfig.lazyLoadingForFullHeight) { + params[Param.IsLazyLoadingForEmbedEnabled] = true; + if (isValidCssMargin(this.viewConfig.lazyLoadingMargin)) { + params[Param.RootMarginForLazyLoad] = this.viewConfig.lazyLoadingMargin; + } + } + } + this.defaultHeight = minimumHeight || defaultHeight || this.defaultHeight; + if (enableVizTransformations !== undefined) { + params[Param.EnableVizTransformations] = enableVizTransformations.toString(); + } + if (preventLiveboardFilterRemoval) { + params[Param.preventLiveboardFilterRemoval] = true; + } + if (!isUndefined(updatedSpotterChatPrompt)) { + params[Param.UpdatedSpotterChatPrompt] = !!updatedSpotterChatPrompt; + } + if (!isUndefined(enableStopAnswerGenerationEmbed)) { + params[Param.EnableStopAnswerGenerationEmbed] = !!enableStopAnswerGenerationEmbed; + } + if (visibleVizs) { + params[Param.visibleVizs] = visibleVizs; + } + params[Param.livedBoardEmbed] = true; + if (vizId) { + params[Param.vizEmbed] = true; + } + if (liveboardV2 !== undefined) { + params[Param.LiveboardV2Enabled] = liveboardV2; + } + if (enable2ColumnLayout !== undefined) { + params[Param.Enable2ColumnLayout] = enable2ColumnLayout; + } + if (hideTabPanel) { + params[Param.HideTabPanel] = hideTabPanel; + } + if (hideLiveboardHeader) { + params[Param.HideLiveboardHeader] = hideLiveboardHeader; + } + if (showLiveboardDescription) { + params[Param.ShowLiveboardDescription] = showLiveboardDescription; + } + if (showLiveboardTitle) { + params[Param.ShowLiveboardTitle] = showLiveboardTitle; + } + if (enableAskSage) { + params[Param.enableAskSage] = enableAskSage; + } + if (oAuthPollingInterval !== undefined) { + params[Param.OauthPollingInterval] = oAuthPollingInterval; + } + if (isForceRedirect) { + params[Param.IsForceRedirect] = isForceRedirect; + } + if (dataSourceId !== undefined) { + params[Param.DataSourceId] = dataSourceId; + } + if (isLiveboardStylingAndGroupingEnabled !== undefined) { + params[Param.IsLiveboardStylingAndGroupingEnabled] = isLiveboardStylingAndGroupingEnabled; + } + if (isPNGInScheduledEmailsEnabled !== undefined) { + params[Param.isPNGInScheduledEmailsEnabled] = isPNGInScheduledEmailsEnabled; + } + if (isLiveboardXLSXCSVDownloadEnabled !== undefined) { + params[Param.isLiveboardXLSXCSVDownloadEnabled] = isLiveboardXLSXCSVDownloadEnabled; + } + if (isGranularXLSXCSVSchedulesEnabled !== undefined) { + params[Param.isGranularXLSXCSVSchedulesEnabled] = isGranularXLSXCSVSchedulesEnabled; + } + if (showSpotterLimitations !== undefined) { + params[Param.ShowSpotterLimitations] = showSpotterLimitations; + } + // Handle spotterChatConfig params + if (spotterChatConfig) { + const { hideToolResponseCardBranding, toolResponseCardBrandingLabel, spotterFileUploadEnabled, spotterFileUploadFileTypes, } = spotterChatConfig; + setParamIfDefined(params, Param.HideToolResponseCardBranding, hideToolResponseCardBranding, true); + setParamIfDefined(params, Param.ToolResponseCardBrandingLabel, toolResponseCardBrandingLabel); + if (spotterFileUploadEnabled !== undefined) { + params[Param.SpotterFileUploadEnabled] = spotterFileUploadEnabled; + } + if (spotterFileUploadFileTypes !== undefined) { + params[Param.SpotterFileUploadFileTypes] = JSON.stringify(spotterFileUploadFileTypes); + } + } + if (isLinkParametersEnabled !== undefined) { + params[Param.isLinkParametersEnabled] = isLinkParametersEnabled; + } + if (isCentralizedLiveboardFilterUXEnabled !== undefined) { + params[Param.isCentralizedLiveboardFilterUXEnabled] = isCentralizedLiveboardFilterUXEnabled; + } + if (isThisPeriodInDateFiltersEnabled !== undefined) { + params[Param.IsThisPeriodInDateFiltersEnabled] = isThisPeriodInDateFiltersEnabled; + } + if (isContinuousLiveboardPDFEnabled !== undefined) { + params[Param.IsWYSIWYGLiveboardPDFEnabled] = isContinuousLiveboardPDFEnabled; + } + if (enableLiveboardDataCache !== undefined) { + params[Param.EnableLiveboardDataCache] = enableLiveboardDataCache; + } + if (newChartsLibrary !== undefined) { + params[Param.EnableNewChartLibrary] = newChartsLibrary; + } + params[Param.LiveboardHeaderSticky] = isLiveboardHeaderSticky; + params[Param.LiveboardHeaderV2] = isLiveboardCompactHeaderEnabled; + params[Param.ShowLiveboardVerifiedBadge] = showLiveboardVerifiedBadge; + params[Param.ShowLiveboardReverifyBanner] = showLiveboardReverifyBanner; + params[Param.HideIrrelevantFiltersInTab] = hideIrrelevantChipsInLiveboardTabs; + params[Param.ShowMaskedFilterChip] = showMaskedFilterChip; + params[Param.IsLiveboardMasterpiecesEnabled] = isLiveboardMasterpiecesEnabled; + params[Param.IsEnhancedFilterInteractivityEnabled] = isEnhancedFilterInteractivityEnabled; + params[Param.DataPanelV2Enabled] = dataPanelV2; + params[Param.EnableCustomColumnGroups] = enableCustomColumnGroups; + params[Param.CoverAndFilterOptionInPDF] = coverAndFilterOptionInPDF; + getQueryParamString(params, true); + return params; + } + getIframeSuffixSrc(liveboardId, vizId, activeTabId, personalizedViewId) { + // Extract view from liveboardId if passed along with it (legacy + // approach) + // View must be appended as query param at the end, not + // embedded in path + let liveboardGuid = liveboardId; + let legacyViewId; + if (liveboardId?.includes('?')) { + const [id, query] = liveboardId.split('?'); + liveboardGuid = id; + const params = new URLSearchParams(query); + legacyViewId = params.get('view') || undefined; + } + // personalizedViewId takes precedence over legacyViewId (when passed + // as part of liveboardId) + const effectiveViewId = personalizedViewId || legacyViewId; + let suffix = `/embed/viz/${liveboardGuid}`; + if (activeTabId) { + suffix = `${suffix}/tab/${activeTabId}`; + } + if (vizId) { + suffix = `${suffix}/${vizId}`; + } + const additionalParams = {}; + if (effectiveViewId) { + additionalParams.view = effectiveViewId; + } + const tsPostHashParams = this.getThoughtSpotPostUrlParams(additionalParams); + suffix = `${suffix}${tsPostHashParams}`; + return suffix; + } + /** + * Construct the URL of the embedded ThoughtSpot Liveboard or visualization + * to be loaded within the iFrame. + */ + getIFrameSrc() { + const { vizId, activeTabId, personalizedViewId } = this.viewConfig; + const liveboardId = this.viewConfig.liveboardId ?? this.viewConfig.pinboardId; + if (!liveboardId) { + this.handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.LIVEBOARD_VIZ_ID_VALIDATION, + code: EmbedErrorCodes.LIVEBOARD_ID_MISSING, + error: ERROR_MESSAGE.LIVEBOARD_VIZ_ID_VALIDATION, + }); + } + return `${this.getRootIframeSrc()}${this.getIframeSuffixSrc(liveboardId, vizId, activeTabId, personalizedViewId)}`; + } + setActiveTab(data) { + if (!this.viewConfig.vizId) { + const prefixPath = this.iFrame.src.split('#/')[1].split('/tab')[0]; + const path = `${prefixPath}/tab/${data.tabId}`; + super.trigger(HostEvent.Navigate, path); + } + } + async showPreviewLoader() { + if (!this.viewConfig.showPreviewLoader || !this.viewConfig.vizId) { + return; + } + try { + const preview = await getPreview(this.thoughtSpotHost, this.viewConfig.vizId, this.viewConfig.liveboardId); + if (!preview.vizContent) { + return; + } + addPreviewStylesIfNotPresent(); + const div = document.createElement('div'); + div.innerHTML = ` +
+ ${preview.vizContent} +
+ `; + const previewDiv = div.firstElementChild; + this.hostElement.appendChild(previewDiv); + this.hostElement.style.position = 'relative'; + this.on(EmbedEvent.Data, () => { + previewDiv.remove(); + }); + } + catch (error) { + console.error('Error fetching preview', error); + } + } + beforePrerenderVisible() { + super.beforePrerenderVisible(); + const embedObj = this.getPreRenderObj(); + this.executeAfterEmbedContainerLoaded(() => { + this.navigateToLiveboard(this.viewConfig.liveboardId, this.viewConfig.vizId, this.viewConfig.activeTabId, this.viewConfig.personalizedViewId); + if (embedObj) { + embedObj.currentLiveboardState = { + liveboardId: this.viewConfig.liveboardId, + vizId: this.viewConfig.vizId, + activeTabId: this.viewConfig.activeTabId, + personalizedViewId: this.viewConfig.personalizedViewId, + }; + } + }); + } + async handleRenderForPrerender() { + if (isUndefined(this.viewConfig.liveboardId)) { + return this.prerenderGeneric(); + } + return super.handleRenderForPrerender(); + } + /** + * Triggers an event to the embedded app + * @param {HostEvent} messageType The event type + * @param {any} data The payload to send with the message + * @returns A promise that resolves with the response from the embedded app + */ + trigger(messageType, data = {}, context) { + const dataWithVizId = data; + if (messageType === HostEvent.SetActiveTab) { + this.setActiveTab(data); + return Promise.resolve(null); + } + if (typeof dataWithVizId === 'object' && this.viewConfig.vizId) { + dataWithVizId.vizId = this.viewConfig.vizId; + } + return super.trigger(messageType, dataWithVizId, context); + } + /** + * Destroys the ThoughtSpot embed, and remove any nodes from the DOM. + * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + */ + destroy() { + super.destroy(); + this.unregisterLazyLoadEvents(); + } + postRender() { + this.registerLazyLoadEvents(); + } + registerLazyLoadEvents() { + if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) { + // TODO: Use passive: true, install modernizr to check for passive + window.addEventListener('resize', this.sendFullHeightLazyLoadData); + window.addEventListener('scroll', this.sendFullHeightLazyLoadData, true); + } + } + unregisterLazyLoadEvents() { + if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) { + window.removeEventListener('resize', this.sendFullHeightLazyLoadData); + window.removeEventListener('scroll', this.sendFullHeightLazyLoadData); + } + } + /** + * Render an embedded ThoughtSpot Liveboard or visualization + * @param renderOptions An object specifying the Liveboard ID, + * visualization ID and the runtime filters. + */ + async render() { + await super.render(); + const src = this.getIFrameSrc(); + await this.renderV1Embed(src); + this.showPreviewLoader(); + this.postRender(); + return this; + } + navigateToLiveboard(liveboardId, vizId, activeTabId, personalizedViewId) { + const path = this.getIframeSuffixSrc(liveboardId, vizId, activeTabId, personalizedViewId); + this.viewConfig.liveboardId = liveboardId; + this.viewConfig.activeTabId = activeTabId; + this.viewConfig.vizId = vizId; + this.viewConfig.personalizedViewId = personalizedViewId; + if (this.isRendered) { + this.trigger(HostEvent.Navigate, path.substring(1)); + } + else if (this.viewConfig.preRenderId) { + this.preRender(true); + } + else { + this.render(); + } + } + /** + * Returns the full url of the Liveboard/visualization which can be used to open + * this Liveboard inside the full ThoughtSpot application in a new tab. + * @returns url string + */ + getLiveboardUrl() { + let url = `${this.thoughtSpotHost}/#/pinboard/${this.viewConfig.liveboardId}`; + if (this.viewConfig.activeTabId) { + url = `${url}/tab/${this.viewConfig.activeTabId}`; + } + if (this.viewConfig.vizId) { + url = `${url}/${this.viewConfig.vizId}`; + } + if (this.viewConfig.personalizedViewId) { + url = `${url}?view=${this.viewConfig.personalizedViewId}`; + } + return url; + } +} +/** + * @hidden + */ +class PinboardEmbed extends LiveboardEmbed { +} + +/** + * Copyright (c) 2022 + * + * Embed ThoughtSpot search or a saved answer. + * https://developers.thoughtspot.com/docs/search-embed + * @summary Search embed + * @author Ayon Ghosh + */ +/** + * Define the initial state of column custom group accordions + * in data panel v2. + */ +var DataPanelCustomColumnGroupsAccordionState; +(function (DataPanelCustomColumnGroupsAccordionState) { + /** + * Expand all the accordion initially in data panel v2. + */ + DataPanelCustomColumnGroupsAccordionState["EXPAND_ALL"] = "EXPAND_ALL"; + /** + * Collapse all the accordions initially in data panel v2. + */ + DataPanelCustomColumnGroupsAccordionState["COLLAPSE_ALL"] = "COLLAPSE_ALL"; + /** + * Expand the first accordion and collapse the rest. + */ + DataPanelCustomColumnGroupsAccordionState["EXPAND_FIRST"] = "EXPAND_FIRST"; +})(DataPanelCustomColumnGroupsAccordionState || (DataPanelCustomColumnGroupsAccordionState = {})); +const HiddenActionItemByDefaultForSearchEmbed = [ + Action.EditACopy, + Action.SaveAsView, + Action.UpdateTML, + Action.EditTML, + Action.AnswerDelete, +]; +/** + * Embed ThoughtSpot search + * @group Embed components + */ +class SearchEmbed extends TsEmbed { + constructor(domSelector, viewConfig) { + viewConfig = { + embedComponentType: 'SearchEmbed', + excludeRuntimeFiltersfromURL: true, + excludeRuntimeParametersfromURL: true, + ...viewConfig, + }; + super(domSelector, viewConfig); + } + /** + * Get the state of the data sources panel that the embedded + * ThoughtSpot search will be initialized with. + */ + getDataSourceMode() { + let dataSourceMode = DataSourceVisualMode.Expanded; + if (this.viewConfig.collapseDataSources === true + || this.viewConfig.collapseDataPanel === true) { + dataSourceMode = DataSourceVisualMode.Collapsed; + } + if (this.viewConfig.hideDataSources === true) { + dataSourceMode = DataSourceVisualMode.Hidden; + } + return dataSourceMode; + } + getSearchInitData() { + return { + ...(this.viewConfig.excludeSearchTokenStringFromURL ? { + searchOptions: { + searchTokenString: this.viewConfig.searchOptions?.searchTokenString, + }, + } : {}), + }; + } + async getAppInitData() { + const defaultAppInitData = await super.getAppInitData(); + const result = { + ...defaultAppInitData, + ...this.getSearchInitData(), + }; + if (this.viewConfig.visualOverrides) { + result.embedParams = { + ...(defaultAppInitData.embedParams || {}), + visualOverridesParams: this.viewConfig.visualOverrides, + }; + } + return result; + } + getEmbedParamsObject() { + const { hideResults, enableSearchAssist, forceTable, searchOptions, runtimeFilters, dataSource, dataSources, excludeRuntimeFiltersfromURL, hideSearchBar, dataPanelV2 = true, useLastSelectedSources = false, runtimeParameters, collapseSearchBarInitially = false, enableCustomColumnGroups = false, dataPanelCustomGroupsAccordionInitialState = DataPanelCustomColumnGroupsAccordionState.EXPAND_ALL, focusSearchBarOnRender = true, excludeRuntimeParametersfromURL, excludeSearchTokenStringFromURL, collapseSearchBar = true, isThisPeriodInDateFiltersEnabled, newChartsLibrary, } = this.viewConfig; + const queryParams = this.getBaseQueryParams(); + queryParams[Param.HideActions] = [ + ...(queryParams[Param.HideActions] ?? []), + ...HiddenActionItemByDefaultForSearchEmbed, + ]; + if (dataSources && dataSources.length) { + queryParams[Param.DataSources] = JSON.stringify(dataSources); + } + if (dataSource) { + queryParams[Param.DataSources] = `["${dataSource}"]`; + } + if (searchOptions?.searchTokenString) { + if (!excludeSearchTokenStringFromURL) { + queryParams[Param.searchTokenString] = encodeURIComponent(searchOptions.searchTokenString); + } + if (searchOptions.executeSearch) { + queryParams[Param.executeSearch] = true; + } + } + if (enableSearchAssist) { + queryParams[Param.EnableSearchAssist] = true; + } + if (hideResults) { + queryParams[Param.HideResult] = true; + } + if (forceTable) { + queryParams[Param.ForceTable] = true; + } + if (hideSearchBar) { + queryParams[Param.HideSearchBar] = true; + } + if (!focusSearchBarOnRender) { + queryParams[Param.FocusSearchBarOnRender] = focusSearchBarOnRender; + } + if (isThisPeriodInDateFiltersEnabled !== undefined) { + queryParams[Param.IsThisPeriodInDateFiltersEnabled] = isThisPeriodInDateFiltersEnabled; + } + if (newChartsLibrary !== undefined) { + queryParams[Param.EnableNewChartLibrary] = newChartsLibrary; + } + queryParams[Param.DataPanelV2Enabled] = dataPanelV2; + queryParams[Param.DataSourceMode] = this.getDataSourceMode(); + queryParams[Param.UseLastSelectedDataSource] = useLastSelectedSources; + if (dataSource || dataSources) { + queryParams[Param.UseLastSelectedDataSource] = false; + } + queryParams[Param.searchEmbed] = true; + queryParams[Param.CollapseSearchBarInitially] = collapseSearchBarInitially || collapseSearchBar; + queryParams[Param.EnableCustomColumnGroups] = enableCustomColumnGroups; + if (dataPanelCustomGroupsAccordionInitialState + === DataPanelCustomColumnGroupsAccordionState.COLLAPSE_ALL + || dataPanelCustomGroupsAccordionInitialState + === DataPanelCustomColumnGroupsAccordionState.EXPAND_FIRST) { + queryParams[Param.DataPanelCustomGroupsAccordionInitialState] = dataPanelCustomGroupsAccordionInitialState; + } + else { + queryParams[Param.DataPanelCustomGroupsAccordionInitialState] = DataPanelCustomColumnGroupsAccordionState.EXPAND_ALL; + } + return queryParams; + } + getEmbedParams() { + const { runtimeParameters, runtimeFilters, excludeRuntimeParametersfromURL, excludeRuntimeFiltersfromURL, } = this.viewConfig; + const queryParams = this.getEmbedParamsObject(); + let query = ''; + const queryParamsString = getQueryParamString(queryParams, true); + if (queryParamsString) { + query = `?${queryParamsString}`; + } + const parameterQuery = getRuntimeParameters(runtimeParameters || []); + if (parameterQuery && !excludeRuntimeParametersfromURL) + query += `&${parameterQuery}`; + const filterQuery = getFilterQuery(runtimeFilters || []); + if (filterQuery && !excludeRuntimeFiltersfromURL) { + query += `&${filterQuery}`; + } + return query; + } + /** + * Construct the URL of the embedded ThoughtSpot search to be + * loaded in the iframe + * @param answerId The GUID of a saved answer + * @param dataSources A list of data source GUIDs + */ + getIFrameSrc() { + const { answerId } = this.viewConfig; + const answerPath = answerId ? `saved-answer/${answerId}` : 'answer'; + const tsPostHashParams = this.getThoughtSpotPostUrlParams(); + return `${this.getRootIframeSrc()}/embed/${answerPath}${tsPostHashParams}`; + } + /** + * Render the embedded ThoughtSpot search + */ + async render() { + await super.render(); + const { answerId } = this.viewConfig; + const src = this.getIFrameSrc(); + await this.renderIFrame(src); + getAuthPromise().then(() => { + if (checkReleaseVersionInBeta(getReleaseVersion(), getEmbedConfig().suppressSearchEmbedBetaWarning + || getEmbedConfig().suppressErrorAlerts)) { + alert(ERROR_MESSAGE.SEARCHEMBED_BETA_WRANING_MESSAGE); + } + }); + return this; + } +} + +/** + * Embed ThoughtSpot search bar + * @version SDK: 1.18.0 | ThoughtSpot: 8.10.0.cl, 9.0.1-sw + * @group Embed components + */ +class SearchBarEmbed extends TsEmbed { + constructor(domSelector, viewConfig) { + super(domSelector); + this.embedComponentType = 'SearchBarEmbed'; + this.viewConfig = viewConfig; + } + getEmbedParamsObject() { + const { searchOptions, dataSource, dataSources, useLastSelectedSources = false, excludeSearchTokenStringFromURL, } = this.viewConfig; + const queryParams = this.getBaseQueryParams(); + queryParams[Param.HideActions] = [...(queryParams[Param.HideActions] ?? [])]; + if (dataSources && dataSources.length) { + queryParams[Param.DataSources] = JSON.stringify(dataSources); + } + if (dataSource) { + queryParams[Param.DataSources] = `["${dataSource}"]`; + } + if (searchOptions?.searchTokenString) { + if (!excludeSearchTokenStringFromURL) { + queryParams[Param.searchTokenString] = encodeURIComponent(searchOptions.searchTokenString); + } + if (searchOptions.executeSearch) { + queryParams[Param.executeSearch] = true; + } + } + queryParams[Param.UseLastSelectedDataSource] = useLastSelectedSources; + if (dataSource || dataSources) { + queryParams[Param.UseLastSelectedDataSource] = false; + } + queryParams[Param.searchEmbed] = true; + return queryParams; + } + /** + * Construct the URL of the embedded ThoughtSpot search to be + * loaded in the iframe + * @param dataSources A list of data source GUIDs + */ + getIFrameSrc() { + const queryParams = this.getEmbedParamsObject(); + const path = 'search-bar-embed'; + let query = ''; + const queryParamsString = getQueryParamString(queryParams, true); + if (queryParamsString) { + query = `?${queryParamsString}`; + } + const tsPostHashParams = this.getThoughtSpotPostUrlParams(); + return `${this.getEmbedBasePath(query)}/embed/${path}${tsPostHashParams}`; + } + /** + * Render the embedded ThoughtSpot search + */ + async render() { + await super.render(); + const src = this.getIFrameSrc(); + await this.renderIFrame(src); + return this; + } + getSearchInitData() { + return { + searchOptions: this.viewConfig.excludeSearchTokenStringFromURL + ? this.viewConfig.searchOptions + : null, + }; + } + async getAppInitData() { + const defaultAppInitData = await super.getAppInitData(); + return { ...defaultAppInitData, ...this.getSearchInitData() }; + } +} + +const createConversation = ` +mutation CreateConversation($params: Input_convassist_CreateConversationRequest) { + ConvAssist__createConversation(request: $params) { + convId + initialCtx { + type + tsAnsCtx { + sessionId + genNo + stateKey { + transactionId + generationNumber + } + worksheet { + worksheetId + worksheetName + } + } + } + } +} +`; +const sendMessage = ` +query SendMessage($params: Input_convassist_SendMessageRequest) { + ConvAssist__sendMessage(request: $params) { + responses { + timestamp + msgId + data { + asstRespData { + tool + asstRespText + nlsAnsData { + sageQuerySuggestions { + sageQueryTokens { + additions { + phrase { + isCompletePhrase + numTokens + phraseType + startIndex + __typename + } + tokens { + token + dataType + typeEnum + guid + tokenMetadata { + name + __typename + } + __typename + } + __typename + } + phrases { + isCompletePhrase + numTokens + phraseType + startIndex + __typename + } + removals { + phrase { + isCompletePhrase + numTokens + phraseType + startIndex + __typename + } + tokens { + token + dataType + typeEnum + guid + tokenMetadata { + name + __typename + } + __typename + } + __typename + } + tokens { + token + dataType + typeEnum + guid + tokenMetadata { + name + __typename + } + __typename + } + __typename + } + llmReasoning { + assumptions + clarifications + interpretation + __typename + } + tokens + tmlTokens + worksheetId + tokens + description + title + tmlTokens + cached + sqlQuery + sessionId + genNo + formulaInfo { + name + expression + __typename + } + tmlPhrases + ambiguousPhrases { + alternativePhrases { + phraseType + token { + token + dataType + typeEnum + guid + tokenMetadata { + name + __typename + } + __typename + } + __typename + } + ambiguityType + token { + token + dataType + typeEnum + guid + tokenMetadata { + name + __typename + } + __typename + } + __typename + } + ambiguousTokens { + alternativeTokens { + token + dataType + typeEnum + guid + tokenMetadata { + name + deprecatedTableGuid + deprecatedTableName + isFormula + rootTables { + created + description + guid + indexVersion + modified + name + __typename + } + schemaTableUserDefinedName + table { + created + description + guid + indexVersion + modified + name + __typename + } + __typename + } + __typename + } + ambiguityType + token { + token + dataType + typeEnum + guid + tokenMetadata { + name + __typename + } + __typename + } + __typename + } + stateKey { + transactionId + generationNumber + transactionId + __typename + } + subQueries { + tokens + cohortConfig { + anchorColumnId + cohortAnswerGuid + cohortGroupingType + cohortGuid + cohortType + combineNonGroupValues + description + groupExcludedQueryValues + hideExcludedQueryValues + isEditable + name + nullOutputValue + returnColumnId + __typename + } + formulas { + name + expression + __typename + } + __typename + } + visualizationSuggestion { + chartType + displayMode + axisConfigs { + category + color + hidden + size + sort + x + y + __typename + } + usersVizIntentApplied + customChartConfigs { + dimensions { + columns + key + __typename + } + key + __typename + } + customChartGuid + __typename + } + tableData { + columnDataLite { + columnId + columnDataType + dataValue + columnName + __typename + } + __typename + } + warningType + cached + __typename + } + debugInfo { + fewShotExamples { + chartType + id + mappingId + nlQuery + nlQueryConcepts + sageQuery + scope + sql + tml + __typename + } + __typename + } + __typename + } + __typename + } + errorData { + tool + errCode + errTxt + toolErrCode + __typename + } + __typename + } + type + __typename + } + prevCtx { + genNo + sessionId + __typename + } + __typename + } +} +`; + +class Conversation { + constructor(thoughtSpotHost, worksheetId) { + this.thoughtSpotHost = thoughtSpotHost; + this.worksheetId = worksheetId; + this.inProgress = null; + this.inProgress = this.init(); + } + async init() { + const { convId } = await this.createConversation(); + this.conversationId = convId; + } + createConversation() { + return this.executeQuery(createConversation, { + params: { + initialCtx: { + tsWorksheetCtx: { + worksheet: { + worksheetId: this.worksheetId, + }, + }, + type: 'TS_WORKSHEET', + }, + userInfo: { + tenantUrl: `${this.thoughtSpotHost}/prism`, + }, + }, + }); + } + async sendMessage(userMessage) { + await this.inProgress; + try { + const { responses } = await this.executeQuery(sendMessage, { + params: { + convId: this.conversationId, + headers: [], + msg: { + data: { + userCmdData: { + cmdText: userMessage, + nlsData: { + worksheetId: this.worksheetId, + questionType: 'ANSWER_SPEC_GENERATION', + }, + }, + }, + msgId: crypto.randomUUID(), + type: 'USER_COMMAND', + }, + }, + }); + const data = responses[0].data; + return { + convId: this.conversationId, + messageId: responses[0].msgId, + data: { + ...data.asstRespData.nlsAnsData.sageQuerySuggestions[0], + convId: this.conversationId, + messageId: responses[0].msgId, + }, + error: null, + }; + } + catch (error) { + return { error }; + } + } + async executeQuery(query, variables) { + return graphqlQuery({ + query, + variables, + thoughtSpotHost: this.thoughtSpotHost, + isCompositeQuery: false, + }); + } +} + +class ConversationMessage extends TsEmbed { + constructor(container, viewConfig) { + viewConfig.embedComponentType = 'bodyless-conversation'; + super(container, viewConfig); + this.viewConfig = viewConfig; + } + getEmbedParamsObject() { + const queryParams = this.getBaseQueryParams(); + queryParams[Param.HideActions] = [...(queryParams[Param.HideActions] ?? [])]; + queryParams[Param.isSpotterAgentEmbed] = true; + return queryParams; + } + getIframeSrc() { + const { sessionId, genNo, acSessionId, acGenNo, convId, messageId, } = this.viewConfig; + const path = 'conv-assist-answer'; + const queryParams = this.getEmbedParamsObject(); + let query = ''; + const queryParamsString = getQueryParamString(queryParams, true); + if (queryParamsString) { + query = `?${queryParamsString}`; + } + const tsPostHashParams = this.getThoughtSpotPostUrlParams({ + sessionId, + genNo, + acSessionId, + acGenNo, + convId, + messageId, + }); + return `${this.getEmbedBasePath(query)}/embed/${path}${tsPostHashParams}`; + } + async render() { + await super.render(); + const src = this.getIframeSrc(); + await this.renderIFrame(src); + return this; + } +} +/** + * Create a conversation embed, which can be integrated inside + * chatbots or other conversational interfaces. + * @version SDK: 1.37.0 | ThoughtSpot: 10.9.0.cl + * @group Embed components + * @example + * ```js + * import { SpotterAgentEmbed } from '@thoughtspot/visual-embed-sdk'; + * + * const conversation = new SpotterAgentEmbed({ + * worksheetId: 'worksheetId', + * }); + * + * const { container, error } = await conversation.sendMessage('show me sales by region'); + * + * // append the container to the DOM + * document.body.appendChild(container); // or to any other element + * ``` + */ +class SpotterAgentEmbed { + constructor(viewConfig) { + this.viewConfig = viewConfig; + const embedConfig = getEmbedConfig(); + this.conversationService = new Conversation(embedConfig.thoughtSpotHost, viewConfig.worksheetId); + } + async sendMessage(userMessage) { + const { data, error } = await this.conversationService.sendMessage(userMessage); + if (error) { + return { error }; + } + const container = document.createElement('div'); + const embed = new ConversationMessage(container, { + ...this.viewConfig, + convId: data.convId, + messageId: data.messageId, + sessionId: data.sessionId, + genNo: data.genNo, + acSessionId: data.stateKey.transactionId, + acGenNo: data.stateKey.generationNumber, + }); + await embed.render(); + return { container, viz: embed }; + } + /** + * Send a message to the conversation service and return only the data. + * @param userMessage - The message to send to the conversation service. + * @returns The data from the conversation service. + */ + async sendMessageData(userMessage) { + try { + const { data, error } = await this.conversationService.sendMessage(userMessage); + if (error) { + return { error }; + } + return { data: { + convId: data.convId, + messageId: data.messageId, + sessionId: data.sessionId, + genNo: data.genNo, + acSessionId: data.stateKey.transactionId, + acGenNo: data.stateKey.generationNumber, + } }; + } + catch (error) { + return { error: error }; + } + } +} +/** + * Create a conversation embed, which can be integrated inside + * chatbots or other conversational interfaces. + * Use {@link SpotterAgentEmbed} instead + * @deprecated from SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + * @group Embed components + * @example + * ```js + * import { SpotterAgentEmbed } from '@thoughtspot/visual-embed-sdk'; + * + * const conversation = new SpotterAgentEmbed({ + * worksheetId: 'worksheetId', + * }); + * + * const { container, error } = await conversation.sendMessage('show me sales by region'); + * + * // append the container to the DOM + * document.body.appendChild(container); // or to any other element + * ``` + */ +class BodylessConversation extends SpotterAgentEmbed { + constructor(viewConfig) { + super(viewConfig); + } +} + +/** + * Embed ThoughtSpot AI Conversation. + * @version SDK: 1.37.0 | ThoughtSpot: 10.9.0.cl + * @group Embed components + * @example + * ```js + * const conversation = new SpotterEmbed('#tsEmbed', { + * worksheetId: 'worksheetId', + * searchOptions: { + * searchQuery: 'searchQuery', + * }, + * }); + * conversation.render(); + * ``` + */ +class SpotterEmbed extends TsEmbed { + constructor(container, viewConfig) { + viewConfig = { + embedComponentType: 'conversation', + excludeRuntimeFiltersfromURL: true, + excludeRuntimeParametersfromURL: true, + ...viewConfig, + }; + super(container, viewConfig); + this.viewConfig = viewConfig; + } + /** + * Extends the default APP_INIT payload with `embedParams.spotterSidebarConfig` + * so the conv-assist app can read sidebar configuration on initialisation. + * + * Precedence for `enablePastConversationsSidebar`: + * `spotterSidebarConfig.enablePastConversationsSidebar` wins over the + * deprecated top-level `enablePastConversationsSidebar` flag; if the former + * is absent the latter is used as a fallback. + * + * An invalid `spotterDocumentationUrl` triggers a validation error and is + * excluded from the payload rather than forwarded to the app. + */ + async getAppInitData() { + const defaultAppInitData = await super.getAppInitData(); + return buildSpotterSidebarAppInitData(defaultAppInitData, this.viewConfig, this.handleError.bind(this)); + } + getEmbedParamsObject() { + const { worksheetId, searchOptions, disableSourceSelection, hideSourceSelection, dataPanelV2, showSpotterLimitations, hideSampleQuestions, runtimeFilters, excludeRuntimeFiltersfromURL, runtimeParameters, excludeRuntimeParametersfromURL, updatedSpotterChatPrompt, enableStopAnswerGenerationEmbed, spotterChatConfig, } = this.viewConfig; + if (!worksheetId) { + this.handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.SPOTTER_EMBED_WORKSHEED_ID_NOT_FOUND, + code: EmbedErrorCodes.WORKSHEET_ID_NOT_FOUND, + error: ERROR_MESSAGE.SPOTTER_EMBED_WORKSHEED_ID_NOT_FOUND, + }); + } + const queryParams = this.getBaseQueryParams(); + queryParams[Param.SpotterEnabled] = true; + // Boolean params + setParamIfDefined(queryParams, Param.DisableSourceSelection, disableSourceSelection, true); + setParamIfDefined(queryParams, Param.HideSourceSelection, hideSourceSelection, true); + setParamIfDefined(queryParams, Param.DataPanelV2Enabled, dataPanelV2, true); + setParamIfDefined(queryParams, Param.ShowSpotterLimitations, showSpotterLimitations, true); + setParamIfDefined(queryParams, Param.HideSampleQuestions, hideSampleQuestions, true); + setParamIfDefined(queryParams, Param.UpdatedSpotterChatPrompt, updatedSpotterChatPrompt, true); + setParamIfDefined(queryParams, Param.EnableStopAnswerGenerationEmbed, enableStopAnswerGenerationEmbed, true); + // Handle spotterChatConfig params + if (spotterChatConfig) { + const { hideToolResponseCardBranding, toolResponseCardBrandingLabel, spotterFileUploadEnabled, spotterFileUploadFileTypes, } = spotterChatConfig; + setParamIfDefined(queryParams, Param.HideToolResponseCardBranding, hideToolResponseCardBranding, true); + setParamIfDefined(queryParams, Param.ToolResponseCardBrandingLabel, toolResponseCardBrandingLabel); + setParamIfDefined(queryParams, Param.SpotterFileUploadEnabled, spotterFileUploadEnabled, true); + if (spotterFileUploadFileTypes !== undefined) { + queryParams[Param.SpotterFileUploadFileTypes] = JSON.stringify(spotterFileUploadFileTypes); + } + } + return queryParams; + } + getIframeSrc() { + const { worksheetId, searchOptions, runtimeFilters, excludeRuntimeFiltersfromURL, runtimeParameters, excludeRuntimeParametersfromURL, } = this.viewConfig; + const path = 'insights/conv-assist'; + const queryParams = this.getEmbedParamsObject(); + let query = ''; + const queryParamsString = getQueryParamString(queryParams, true); + if (queryParamsString) { + query = `?${queryParamsString}`; + } + const filterQuery = getFilterQuery(runtimeFilters || []); + if (filterQuery && !excludeRuntimeFiltersfromURL) { + query += `&${filterQuery}`; + } + const parameterQuery = getRuntimeParameters(runtimeParameters || []); + if (parameterQuery && !excludeRuntimeParametersfromURL) { + query += `&${parameterQuery}`; + } + const tsPostHashParams = this.getThoughtSpotPostUrlParams({ + worksheet: worksheetId, + query: searchOptions?.searchQuery || '', + }); + return `${this.getEmbedBasePath(query)}/embed/${path}${tsPostHashParams}`; + } + async render() { + await super.render(); + const src = this.getIframeSrc(); + await this.renderIFrame(src); + return this; + } +} +/** + * Embed ThoughtSpot AI Conversation. + * Use {@link SpotterEmbed} instead + * @version SDK: 1.37.0 | ThoughtSpot: 10.9.0.cl + * @deprecated from SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl + * @group Embed components + * @example + * ```js + * const conversation = new SpotterEmbed('#tsEmbed', { + * worksheetId: 'worksheetId', + * searchOptions: { + * searchQuery: 'searchQuery', + * }, + * }); + * conversation.render(); + * ``` + */ +class ConversationEmbed extends SpotterEmbed { + constructor(container, viewConfig) { + viewConfig = { + embedComponentType: 'conversation', + excludeRuntimeFiltersfromURL: true, + excludeRuntimeParametersfromURL: true, + ...viewConfig, + }; + super(container, viewConfig); + this.viewConfig = viewConfig; + } +} + +const getAnswerSessionFromQuery = ` +query GetEurekaResults($params: Input_eureka_SearchRequest) { + queryRequest(request: $params) { + ...eurekaResults + __typename + } + } + + fragment eurekaResults on eureka_SearchResponse { + facets { + facetType + facetValue + facetValues { + id + resultCount + name + __typename + } + __typename + } + requestIdentifiers { + apiRequestId + appActivityId + __typename + } + sageQuerySuggestions { + llmReasoning { + assumptions + clarifications + interpretation + __typename + } + tokens + tmlTokens + worksheetId + description + title + tmlTokens + formulaInfo { + name + expression + __typename + } + tmlPhrases + ambiguousPhrases { + alternativePhrases { + phraseType + token { + token + dataType + typeEnum + guid + tokenMetadata { + name + __typename + } + __typename + } + __typename + } + ambiguityType + token { + token + dataType + typeEnum + guid + tokenMetadata { + name + __typename + } + __typename + } + __typename + } + ambiguousTokens { + alternativeTokens { + token + dataType + typeEnum + guid + tokenMetadata { + name + deprecatedTableGuid + deprecatedTableName + isFormula + rootTables { + created + description + guid + indexVersion + modified + name + __typename + } + schemaTableUserDefinedName + table { + created + description + guid + indexVersion + modified + name + __typename + } + __typename + } + __typename + } + ambiguityType + token { + token + dataType + typeEnum + guid + tokenMetadata { + name + __typename + } + __typename + } + __typename + } + sessionId + genNo + stateKey { + generationNumber + transactionId + __typename + } + subQueries { + tokens + cohortConfig { + anchorColumnId + cohortAnswerGuid + cohortGroupingType + cohortGuid + cohortType + combineNonGroupValues + description + groupExcludedQueryValues + hideExcludedQueryValues + isEditable + name + nullOutputValue + returnColumnId + __typename + } + formulas { + name + expression + __typename + } + __typename + } + visualizationSuggestion { + chartType + displayMode + axisConfigs { + category + color + hidden + size + sort + x + y + __typename + } + usersVizIntentApplied + customChartConfigs { + dimensions { + columns + key + __typename + } + key + __typename + } + customChartGuid + __typename + } + tableData { + columnDataLite { + columnId + columnDataType + dataValue + columnName + __typename + } + __typename + } + warningType + cached + warningDetails { + warningType + __typename + } + __typename + } + results { + objectSecurityInfo { + objectType + objectId + objectIdForDeletionCheck + objectTypeForDeletionCheck + isD13ySourced + offset + __typename + } + searchAnswer { + ...eurekaAnswer + __typename + } + searchPinboardViz { + answer { + ...eurekaAnswer + __typename + } + pinboardHeader { + id + title + __typename + } + __typename + } + searchPinboard { + header { + ...header + __typename + } + usageInfo { + ...usageInfo + __typename + } + answers { + ...eurekaAnswer + __typename + } + vizCount { + charts + metrics + tables + __typename + } + __typename + } + snippetInfo { + titleSnippet { + snippetString + highlights { + start + end + __typename + } + __typename + } + descriptionSnippet { + snippetString + highlights { + start + end + __typename + } + __typename + } + sageQuerySnippet { + phrase { + isCompletePhrase + numTokens + phraseType + startIndex + __typename + } + token { + token + dataType + typeEnum + __typename + } + __typename + } + sageQuerySnippetWithHighlights { + highlights { + start + end + __typename + } + phraseType + phraseValue + __typename + } + __typename + } + score + debugInfo + resultType + sageQuery + __typename + } + version + nextPageOffset + batchSizeRequired + isFinalPage + totalResults + totalFacetResultCount + errorCode + debugInfo { + fewShotExamples { + chartType + formulas { + name + expression + __typename + } + id + mappingId + nlQuery + nlQueryConcepts + sageQuery + scope + sql + tml + feedbackType + __typename + } + __typename + } + __typename + } + + fragment eurekaAnswer on eureka_AnswerResult { + header { + ...header + __typename + } + usageInfo { + ...usageInfo + __typename + } + preferredViz { + ...visualizationMetadata + __typename + } + worksheetInfo { + ...worksheetInfo + __typename + } + formatted { + phrase { + isCompletePhrase + numTokens + phraseType + startIndex + __typename + } + token { + token + typeEnum + __typename + } + __typename + } + __typename + } + + fragment header on eureka_Header { + id + title + description + authorGuid + authorName + createdOn + tagIds + __typename + } + + fragment usageInfo on eureka_UsageInfo { + favouriteCount + viewCount + __typename + } + + fragment visualizationMetadata on eureka_VisualizationMetadata { + vizType + chartType + vizSnapshotRequestData { + parentReportbookGuid + parentType + version + vizGuid + __typename + } + __typename + } + + fragment worksheetInfo on eureka_WorksheetInfo { + id + name + __typename + }`; + +/** + * Get answer from natural language query + * @param query string + * @param worksheetId string + * @returns AnswerService and the suggestion response. + * @version SDK: 1.33.1 | ThoughtSpot: 10.3.0.cl + * @example + * ```js + * const { answer } = await getAnswerFromQuery('revenue', 'worksheetId'); + * ``` + */ +const getAnswerFromQuery = async (query, worksheetId) => { + const embedConfig = getEmbedConfig(); + const resp = await graphqlQuery({ + query: getAnswerSessionFromQuery, + variables: { + params: { + facetSelections: [], + filterSelections: [ + { + facetType: 'WORKSHEETS', + facetValue: [ + worksheetId, + ], + }, + ], + query, + worksheetFacetPayload: { + worksheetId, + }, + searchOption: 'AI_ANSWER', + }, + }, + thoughtSpotHost: embedConfig.thoughtSpotHost, + isCompositeQuery: false, + }); + const suggestion = resp.sageQuerySuggestions[0]; + const answerSession = { + sessionId: suggestion.sessionId, + genNo: suggestion.genNo, + acSession: { + sessionId: suggestion.stateKey.transactionId, + genNo: suggestion.stateKey.generationNumber, + }, + }; + return { + answer: new AnswerService(answerSession, null, embedConfig.thoughtSpotHost), + suggestion, + }; +}; + +/** + * Creates a new Liveboard in ThoughtSpot using the provided AnswerService instances. + * + * Each answer will be added as a visualization to the newly created Liveboard. + * + * @param {AnswerService[]} answers - An array of initialized `AnswerService` instances + * representing the answers to be added to the Liveboard. + * @param {string} name - The name of the Liveboard to create. + * @returns result Promise + * @version SDK: 1.33.1 | ThoughtSpot: * + * @example + * ```js + * import { EmbedEvent, AnswerService } from "@thoughtspot/visual-embed-sdk"; + * + * embed.on(EmbedEvent.Data, async () => { + * try { + * const answerService = await embed.getAnswerService(); + * const lb = await createLiveboardWithAnswers( + * [answerService], + * "My Liveboard" + * ); + * console.log("Liveboard created:", lb); + * } catch (err) { + * console.error("Failed to create liveboard:", err); + * } + * }); + * ``` + */ +const createLiveboardWithAnswers = async (answers, name) => { + const { thoughtSpotHost, authType } = getEmbedConfig(); + const resp = await tokenizedFetch(`${thoughtSpotHost}/api/rest/2.0/metadata/search`, { + method: 'POST', + credentials: 'include', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify({ + metadata: [{ + type: 'LIVEBOARD', + identifier: name, + }], + }), + }); + const lbList = await resp.json(); + const liveboardId = lbList[0]?.metadata_id; + const answerTMLs = (await Promise.all(answers.map((a) => a.getTML()))) + .filter((tml) => tml.answer.search_query); + const lbTml = { + guid: liveboardId, + liveboard: { + name, + visualizations: answerTMLs.map((tml, idx) => ({ + id: `Viz_${idx}`, + answer: tml.answer, + })), + layout: { + tiles: answerTMLs.map((tml, idx) => ({ + visualization_id: `Viz_${idx}`, + size: 'MEDIUM_SMALL', + })), + }, + }, + }; + const result = await executeTML({ + metadata_tmls: [JSON.stringify(lbTml)], + import_policy: 'ALL_OR_NONE', + }); + return result; +}; + +/** + * Starts an automatic renderer that watches the DOM for iframes containing + * the `tsmcp=true` query parameter and replaces them with fully configured + * ThoughtSpot embed iframes. The query parameter is automatically added by + * the ThoughtSpot MCP server. + * + * A {@link MutationObserver} is set up on `document.body` to detect both + * directly added iframes and iframes nested within added container elements. + * Each matching iframe is replaced in-place with a new ThoughtSpot embed + * iframe that merges the original iframe's query parameters with the SDK + * embed parameters. + * + * Call {@link MutationObserver.disconnect | observer.disconnect()} on the + * returned observer to stop monitoring the DOM. + * + * @param viewConfig - Optional configuration for the auto-rendered embeds. + * Accepts all properties from {@link AutoMCPFrameRendererViewConfig}. + * Defaults to an empty config. + * @returns A {@link MutationObserver} instance that is actively observing + * `document.body`. Disconnect it when monitoring is no longer needed. + * + * @example + * ```js + * import { startAutoMCPFrameRenderer } from '@thoughtspot/visual-embed-sdk'; + * + * // Start watching the DOM for tsmcp iframes + * const observer = startAutoMCPFrameRenderer({ + * // optional view config overrides + * }); + * + * // Later, stop watching + * observer.disconnect(); + * ``` + * + * @example + * Detailed example of how to use the auto-frame renderer: + * [Python React Agent Simple UI](https://github.com/thoughtspot/developer-examples/tree/main/mcp/python-react-agent-simple-ui) + */ +function startAutoMCPFrameRenderer(viewConfig = {}) { + const replaceWithMCPIframe = (iframe) => { + const autoMCPFrameRenderer = new AutoFrameRenderer(viewConfig); + autoMCPFrameRenderer.replaceIframe(iframe); + }; + const observer = new MutationObserver((mutations) => { + for (const mutation of mutations) { + for (const node of Array.from(mutation.addedNodes)) { + if (node instanceof HTMLIFrameElement && isTSMCPIframe(node)) { + replaceWithMCPIframe(node); + } + if (node instanceof HTMLElement) { + node.querySelectorAll('iframe').forEach((iframe) => { + if (isTSMCPIframe(iframe)) { + replaceWithMCPIframe(iframe); + } + }); + } + } + } + }); + observer.observe(document.body, { childList: true, subtree: true }); + return observer; +} +function isTSMCPIframe(iframe) { + try { + const url = new URL(iframe.src); + return url.searchParams.get(Param.Tsmcp) === 'true'; + } + catch (e) { + // The iframe src might not be a valid URL (e.g., 'about:blank'). + return false; + } +} +/** + * Embed component that automatically replaces a plain iframe with a + * ThoughtSpot embed iframe. It merges the SDK's embed parameters with + * the original iframe's query parameters (stripping the `tsmcp` marker) + * and swaps the original iframe element in the DOM. + * + * This class is used internally by {@link startAutoMCPFrameRenderer} and + * is not intended to be instantiated directly. + */ +class AutoFrameRenderer extends TsEmbed { + constructor(viewConfig) { + viewConfig.embedComponentType = 'auto-frame-renderer'; + const container = document.createElement('div'); + super(container, viewConfig); + this.viewConfig = viewConfig; + } + /** + * Builds the final iframe `src` by merging the SDK embed parameters + * with the query parameters already present on the source iframe URL. + * The `tsmcp` marker param is removed so it does not propagate to the + * ThoughtSpot application. + * + * @param sourceSrc - The original iframe's `src` URL string. + * @returns The constructed URL to use for the ThoughtSpot embed iframe. + */ + getMCPIframeSrc(sourceSrc) { + const queryParams = this.getEmbedParamsObject(); + const sourceURL = new URL(sourceSrc); + const existingQueryParams = sourceURL.searchParams; + const existingQueryParamsObject = Object.fromEntries(existingQueryParams); + delete existingQueryParamsObject[Param.Tsmcp]; + const mergedQueryParams = { ...queryParams, ...existingQueryParamsObject }; + const mergedQueryParamsString = getQueryParamString(mergedQueryParams, true); + const queryString = mergedQueryParamsString ? `?${mergedQueryParamsString}` : ''; + const frameSrc = `${this.getEmbedBasePath(queryString)}${sourceURL.hash.replace('#', '')}`; + return frameSrc; + } + /** + * Overrides the base insertion behavior so the new embed iframe + * replaces the original iframe in-place rather than being appended + * to a container element. Falls back to the default behavior when + * no iframe has been set for replacement. + */ + handleInsertionIntoDOM(child) { + if (this.frameToReplace) { + this.frameToReplace.replaceWith(child); + } + else { + super.handleInsertionIntoDOM(child); + } + } + /** + * Replaces the given iframe with a new ThoughtSpot embed iframe. + * + * The original iframe's `src` is used to derive the embed URL, and + * once the new iframe is rendered it takes the original's place in + * the DOM tree. + * + * @param iframe - The existing `