From e0edaf405c6e90e3e12efcad3f2a77a5b6480418 Mon Sep 17 00:00:00 2001 From: sunghyunkang1111 <114709653+sunghyunkang1111@users.noreply.github.com> Date: Mon, 7 Jul 2025 11:00:29 -0500 Subject: [PATCH] MSRC fixes for testExplorer and HeatMap (#2183) * MSRC fixes for testExplorer and HeatMap * MSRC fixes for testExplorer and HeatMap --- .eslintignore | 2 - src/ConfigContext.ts | 3 - src/Controls/Heatmap/Heatmap.html | 11 - src/Controls/Heatmap/Heatmap.less | 55 ----- src/Controls/Heatmap/Heatmap.test.ts | 143 ------------ src/Controls/Heatmap/Heatmap.ts | 272 ----------------------- src/Controls/Heatmap/HeatmapDatatypes.ts | 106 --------- test/testExplorer/TestExplorer.ts | 2 +- webpack.config.js | 86 +++---- 9 files changed, 46 insertions(+), 634 deletions(-) delete mode 100644 src/Controls/Heatmap/Heatmap.html delete mode 100644 src/Controls/Heatmap/Heatmap.less delete mode 100644 src/Controls/Heatmap/Heatmap.test.ts delete mode 100644 src/Controls/Heatmap/Heatmap.ts delete mode 100644 src/Controls/Heatmap/HeatmapDatatypes.ts diff --git a/.eslintignore b/.eslintignore index 2eeecaed5..f97291787 100644 --- a/.eslintignore +++ b/.eslintignore @@ -23,8 +23,6 @@ src/Common/MongoUtility.ts src/Common/NotificationsClientBase.ts src/Common/QueriesClient.ts src/Common/Splitter.ts -src/Controls/Heatmap/Heatmap.test.ts -src/Controls/Heatmap/Heatmap.ts src/Definitions/datatables.d.ts src/Definitions/gif.d.ts src/Definitions/globals.d.ts diff --git a/src/ConfigContext.ts b/src/ConfigContext.ts index a29f59619..da35f7839 100644 --- a/src/ConfigContext.ts +++ b/src/ConfigContext.ts @@ -76,9 +76,6 @@ let configContext: Readonly = { `^https:\\/\\/cosmos-db-dataexplorer-germanycentral\\.azurewebsites\\.de$`, `^https:\\/\\/.*\\.fabric\\.microsoft\\.com$`, `^https:\\/\\/.*\\.powerbi\\.com$`, - `^https:\\/\\/.*\\.analysis-df\\.net$`, - `^https:\\/\\/.*\\.analysis-df\\.windows\\.net$`, - `^https:\\/\\/.*\\.azure-test\\.net$`, `^https:\\/\\/dataexplorer-preview\\.azurewebsites\\.net$`, ], // Webpack injects this at build time gitSha: process.env.GIT_SHA, diff --git a/src/Controls/Heatmap/Heatmap.html b/src/Controls/Heatmap/Heatmap.html deleted file mode 100644 index c397faf3b..000000000 --- a/src/Controls/Heatmap/Heatmap.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - -
- - diff --git a/src/Controls/Heatmap/Heatmap.less b/src/Controls/Heatmap/Heatmap.less deleted file mode 100644 index 108ade9b4..000000000 --- a/src/Controls/Heatmap/Heatmap.less +++ /dev/null @@ -1,55 +0,0 @@ -@import "../../../less/Common/Constants"; -html { - font-family: @DataExplorerFont; - padding: 0px; - margin: 0px; - border: 0px; - overflow: hidden; - position: fixed; - width: 100%; - height: 100%; -} - -body { - font-family: @DataExplorerFont; - padding: 0px; - margin: 0px; - border: 0px; - overflow: hidden; -} - -#heatmap { - .dark-theme { - color: @BaseLight; - } - - .chartTitle { - position: absolute; - top: 5px; - left: 3px; - font-size: 13px; - } - - .noDataMessage { - display: flex; - justify-content: center; - align-items: center; - position: absolute; - z-index: 10000; - height: 100%; - width: 100%; - top: 0; - left: 0; - opacity: 0.97; - div { - border-color: rgba(204, 204, 204, 0.8); - box-shadow: 0 4px 6px 0 rgba(0, 0, 0, 0.12); - padding: 15px 10px; - width: calc(55% - 40px); - font-size: 13px; - text-align: center; - border-width: 1px; - border-style: solid; - } - } -} diff --git a/src/Controls/Heatmap/Heatmap.test.ts b/src/Controls/Heatmap/Heatmap.test.ts deleted file mode 100644 index e51c91285..000000000 --- a/src/Controls/Heatmap/Heatmap.test.ts +++ /dev/null @@ -1,143 +0,0 @@ -import dayjs from "dayjs"; -import { handleMessage, Heatmap, isDarkTheme } from "./Heatmap"; -import { PortalTheme } from "./HeatmapDatatypes"; - -describe("The Heatmap Control", () => { - const dataPoints = { - "1": { - "2019-06-19T00:59:10Z": { - "Normalized Throughput": 0.35, - }, - "2019-06-19T00:48:10Z": { - "Normalized Throughput": 0.25, - }, - }, - }; - - const chartCaptions = { - chartTitle: "chart title", - yAxisTitle: "YAxisTitle", - tooltipText: "Tooltip text", - timeWindow: 123456789, - }; - - let heatmap: Heatmap; - const theme: PortalTheme = 1; - const divElement = `
`; - - describe("drawHeatmap rendering", () => { - beforeEach(() => { - heatmap = new Heatmap(dataPoints, chartCaptions, theme); - document.body.innerHTML = divElement; - }); - - afterEach(() => { - document.body.innerHTML = ``; - }); - - it("should call _getChartSettings when drawHeatmap is invoked", () => { - const _getChartSettings = jest.spyOn(heatmap, "_getChartSettings"); - heatmap.drawHeatmap(); - expect(_getChartSettings).toHaveBeenCalled(); - }); - - it("should call _getLayoutSettings when drawHeatmap is invoked", () => { - const _getLayoutSettings = jest.spyOn(heatmap, "_getLayoutSettings"); - heatmap.drawHeatmap(); - expect(_getLayoutSettings).toHaveBeenCalled(); - }); - - it("should call _getChartDisplaySettings when drawHeatmap is invoked", () => { - const _getChartDisplaySettings = jest.spyOn(heatmap, "_getChartDisplaySettings"); - heatmap.drawHeatmap(); - expect(_getChartDisplaySettings).toHaveBeenCalled(); - }); - - it("drawHeatmap should render a Heatmap inside the div element", () => { - heatmap.drawHeatmap(); - expect(document.body.innerHTML).not.toEqual(divElement); - }); - }); - - describe("generateMatrixFromMap", () => { - it("should massage input data to match output expected", () => { - expect(heatmap.generateMatrixFromMap(dataPoints).yAxisPoints).toEqual(["1"]); - expect(heatmap.generateMatrixFromMap(dataPoints).dataPoints).toEqual([[0.25, 0.35]]); - expect(heatmap.generateMatrixFromMap(dataPoints).xAxisPoints.length).toEqual(2); - }); - - it("should output the date format to ISO8601 string format", () => { - expect(heatmap.generateMatrixFromMap(dataPoints).xAxisPoints[0].slice(10, 11)).toEqual("T"); - expect(heatmap.generateMatrixFromMap(dataPoints).xAxisPoints[0].slice(-1)).toEqual("Z"); - }); - - it("should convert the time to the user's local time", () => { - if (dayjs().utcOffset()) { - expect(heatmap.generateMatrixFromMap(dataPoints).xAxisPoints).not.toEqual([ - "2019-06-19T00:48:10Z", - "2019-06-19T00:59:10Z", - ]); - } else { - expect(heatmap.generateMatrixFromMap(dataPoints).xAxisPoints).toEqual([ - "2019-06-19T00:48:10Z", - "2019-06-19T00:59:10Z", - ]); - } - }); - }); - - describe("isDarkTheme", () => { - it("isDarkTheme should return the correct result", () => { - expect(isDarkTheme(PortalTheme.dark)).toEqual(true); - expect(isDarkTheme(PortalTheme.azure)).not.toEqual(true); - }); - }); -}); - -describe("iframe rendering when there is no data", () => { - afterEach(() => { - document.body.innerHTML = ``; - }); - - it("should show a no data message with a dark theme", () => { - const data = { - data: { - signature: "pcIframe", - data: { - chartData: {}, - chartSettings: {}, - theme: 4, - }, - }, - origin: "http://localhost", - }; - - const divElement = `
`; - document.body.innerHTML = divElement; - - handleMessage(data as MessageEvent); - expect(document.body.innerHTML).toContain("dark-theme"); - expect(document.body.innerHTML).toContain("noDataMessage"); - }); - - it("should show a no data message with a white theme", () => { - const data = { - data: { - signature: "pcIframe", - data: { - chartData: {}, - chartSettings: {}, - theme: 2, - }, - }, - origin: "http://localhost", - }; - - const divElement = `
`; - document.body.innerHTML = divElement; - - handleMessage(data as MessageEvent); - expect(document.body.innerHTML).not.toContain("dark-theme"); - expect(document.body.innerHTML).toContain("noDataMessage"); - }); -}); diff --git a/src/Controls/Heatmap/Heatmap.ts b/src/Controls/Heatmap/Heatmap.ts deleted file mode 100644 index 533341677..000000000 --- a/src/Controls/Heatmap/Heatmap.ts +++ /dev/null @@ -1,272 +0,0 @@ -import dayjs from "dayjs"; -import * as Plotly from "plotly.js-cartesian-dist-min"; -import { sendCachedDataMessage, sendReadyMessage } from "../../Common/MessageHandler"; -import { StyleConstants } from "../../Common/StyleConstants"; -import { MessageTypes } from "../../Contracts/ExplorerContracts"; -import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation"; -import "./Heatmap.less"; -import { - ChartSettings, - DataPayload, - DisplaySettings, - FontSettings, - HeatmapCaptions, - HeatmapData, - LayoutSettings, - PartitionTimeStampToData, - PortalTheme, -} from "./HeatmapDatatypes"; - -export class Heatmap { - public static readonly elementId: string = "heatmap"; - - private _chartData: HeatmapData; - private _heatmapCaptions: HeatmapCaptions; - private _theme: PortalTheme; - private _defaultFontColor: string; - - constructor(data: DataPayload, heatmapCaptions: HeatmapCaptions, theme: PortalTheme) { - this._theme = theme; - this._defaultFontColor = StyleConstants.BaseDark; - this._setThemeColorForChart(); - this._chartData = this.generateMatrixFromMap(data); - this._heatmapCaptions = heatmapCaptions; - } - - private _setThemeColorForChart() { - if (isDarkTheme(this._theme)) { - this._defaultFontColor = StyleConstants.BaseLight; - } - } - - private _getFontStyles(size: number = StyleConstants.MediumFontSize, color = "#838383"): FontSettings { - return { - family: StyleConstants.DataExplorerFont, - size, - color, - }; - } - - public generateMatrixFromMap(data: DataPayload): HeatmapData { - // all keys in data payload, sorted... - const rows: string[] = Object.keys(data).sort((a: string, b: string) => { - if (parseInt(a) < parseInt(b)) { - return -1; - } else { - if (parseInt(a) > parseInt(b)) { - return 1; - } else { - return 0; - } - } - }); - const output: HeatmapData = { - yAxisPoints: [], - dataPoints: [], - xAxisPoints: Object.keys(data[rows[0]]).sort((a: string, b: string) => { - if (a < b) { - return -1; - } else { - if (a > b) { - return 1; - } else { - return 0; - } - } - }), - }; - // go thru all rows and create 2d matrix for heatmap... - for (let i = 0; i < rows.length; i++) { - output.yAxisPoints.push(rows[i]); - const dataPoints: number[] = []; - for (let a = 0; a < output.xAxisPoints.length; a++) { - const row: PartitionTimeStampToData = data[rows[i]]; - dataPoints.push(row[output.xAxisPoints[a]]["Normalized Throughput"]); - } - output.dataPoints.push(dataPoints); - } - for (let a = 0; a < output.xAxisPoints.length; a++) { - const dateTime = output.xAxisPoints[a]; - // convert to local users timezone... - const day = dayjs(new Date(dateTime)).format("YYYY-MM-DD"); - const hour = dayjs(new Date(dateTime)).format("HH:mm:ss"); - // coerce to ISOString format since that is what plotly wants... - output.xAxisPoints[a] = `${day}T${hour}Z`; - } - return output; - } - - // public for testing purposes - public _getChartSettings(): ChartSettings[] { - return [ - { - z: this._chartData.dataPoints, - type: "heatmap", - zmin: 0, - zmid: 50, - zmax: 100, - colorscale: [ - [0.0, "#1FD338"], - [0.1, "#1CAD2F"], - [0.2, "#50A527"], - [0.3, "#719F21"], - [0.4, "#95991B"], - [0.5, "#CE8F11"], - [0.6, "#E27F0F"], - [0.7, "#E46612"], - [0.8, "#E64914"], - [0.9, "#B80016"], - [1.0, "#B80016"], - ], - name: "", - hovertemplate: this._heatmapCaptions.tooltipText, - colorbar: { - thickness: 15, - outlinewidth: 0, - tickcolor: StyleConstants.BaseDark, - tickfont: this._getFontStyles(10, this._defaultFontColor), - }, - y: this._chartData.yAxisPoints, - x: this._chartData.xAxisPoints, - }, - ]; - } - - // public for testing purposes - public _getLayoutSettings(): LayoutSettings { - return { - margin: { - l: 40, - r: 10, - b: 35, - t: 30, - pad: 0, - }, - paper_bgcolor: "transparent", - plot_bgcolor: "transparent", - width: 462, - height: 240, - yaxis: { - title: this._heatmapCaptions.yAxisTitle, - titlefont: this._getFontStyles(11), - autorange: true, - showgrid: false, - zeroline: false, - showline: false, - autotick: true, - fixedrange: true, - ticks: "", - showticklabels: false, - }, - xaxis: { - fixedrange: true, - title: "*White area in heatmap indicates there is no available data", - titlefont: this._getFontStyles(11), - autorange: true, - showgrid: false, - zeroline: false, - showline: false, - autotick: true, - tickformat: this._heatmapCaptions.timeWindow > 7 ? "%I:%M %p" : "%b %e", - showticklabels: true, - tickfont: this._getFontStyles(10), - }, - title: { - text: this._heatmapCaptions.chartTitle, - x: 0.01, - font: this._getFontStyles(13, this._defaultFontColor), - }, - }; - } - - // public for testing purposes - public _getChartDisplaySettings(): DisplaySettings { - return { - /* heatmap can be fully responsive however the min-height needed in that case is greater than the iframe portal height, hence explicit width + height have been set in _getLayoutSettings - responsive: true,*/ - displayModeBar: false, - }; - } - - public drawHeatmap(): void { - // todo - create random elementId generator so multiple heatmaps can be created - ticket # 431469 - Plotly.plot( - Heatmap.elementId, - this._getChartSettings(), - this._getLayoutSettings(), - this._getChartDisplaySettings(), - ); - const plotDiv: any = document.getElementById(Heatmap.elementId); - plotDiv.on("plotly_click", (data: any) => { - let timeSelected: string = data.points[0].x; - timeSelected = timeSelected.replace(" ", "T"); - timeSelected = `${timeSelected}Z`; - let xAxisIndex = 0; - for (let i = 0; i < this._chartData.xAxisPoints.length; i++) { - if (this._chartData.xAxisPoints[i] === timeSelected) { - xAxisIndex = i; - break; - } - } - const output = []; - for (let i = 0; i < this._chartData.dataPoints.length; i++) { - output.push(this._chartData.dataPoints[i][xAxisIndex]); - } - sendCachedDataMessage(MessageTypes.LogInfo, output); - }); - } -} - -export function isDarkTheme(theme: PortalTheme) { - return theme === PortalTheme.dark; -} - -export function handleMessage(event: MessageEvent) { - if (isInvalidParentFrameOrigin(event)) { - return; - } - - if (typeof event.data !== "object" || event.data["signature"] !== "pcIframe") { - return; - } - if ( - typeof event.data.data !== "object" || - !("chartData" in event.data.data) || - !("chartSettings" in event.data.data) - ) { - return; - } - Plotly.purge(Heatmap.elementId); - - document.getElementById(Heatmap.elementId)!.innerHTML = ""; - const data = event.data.data; - const chartData: DataPayload = data.chartData; - const chartSettings: HeatmapCaptions = data.chartSettings; - const chartTheme: PortalTheme = data.theme; - if (Object.keys(chartData).length) { - new Heatmap(chartData, chartSettings, chartTheme).drawHeatmap(); - } else { - const chartTitleElement = document.createElement("div"); - chartTitleElement.innerHTML = data.chartSettings.chartTitle; - chartTitleElement.classList.add("chartTitle"); - - const noDataMessageElement = document.createElement("div"); - noDataMessageElement.classList.add("noDataMessage"); - const noDataMessageContent = document.createElement("div"); - noDataMessageContent.innerHTML = data.errorMessage; - - noDataMessageElement.appendChild(noDataMessageContent); - - if (isDarkTheme(chartTheme)) { - chartTitleElement.classList.add("dark-theme"); - noDataMessageElement.classList.add("dark-theme"); - noDataMessageContent.classList.add("dark-theme"); - } - - document.getElementById(Heatmap.elementId)!.appendChild(chartTitleElement); - document.getElementById(Heatmap.elementId)!.appendChild(noDataMessageElement); - } -} - -window.addEventListener("message", handleMessage, false); -sendReadyMessage(); diff --git a/src/Controls/Heatmap/HeatmapDatatypes.ts b/src/Controls/Heatmap/HeatmapDatatypes.ts deleted file mode 100644 index 3ef9a7109..000000000 --- a/src/Controls/Heatmap/HeatmapDatatypes.ts +++ /dev/null @@ -1,106 +0,0 @@ -type dataPoint = string | number; - -export interface DataPayload { - [id: string]: PartitionTimeStampToData; -} - -export enum PortalTheme { - blue = 1, - azure, - light, - dark, -} - -export interface HeatmapData { - yAxisPoints: string[]; - xAxisPoints: string[]; - dataPoints: dataPoint[][]; -} - -export interface HeatmapCaptions { - chartTitle: string; - yAxisTitle: string; - tooltipText: string; - timeWindow: number; -} - -export interface FontSettings { - family: string; - size: number; - color: string; -} - -export interface LayoutSettings { - paper_bgcolor?: string; - plot_bgcolor?: string; - margin?: { - l: number; - r: number; - b: number; - t: number; - pad: number; - }; - width?: number; - height?: number; - yaxis?: { - fixedrange: boolean; - title: HeatmapCaptions["yAxisTitle"]; - titlefont: FontSettings; - autorange: boolean; - showgrid: boolean; - zeroline: boolean; - showline: boolean; - autotick: boolean; - ticks: ""; - showticklabels: boolean; - }; - xaxis?: { - fixedrange: boolean; - title: string; - titlefont: FontSettings; - autorange: boolean; - showgrid: boolean; - zeroline: boolean; - showline: boolean; - autotick: boolean; - showticklabels: boolean; - tickformat: string; - tickfont: FontSettings; - }; - title?: { - text: HeatmapCaptions["chartTitle"]; - x: number; - font?: FontSettings; - }; - font?: FontSettings; -} - -export interface ChartSettings { - z: HeatmapData["dataPoints"]; - type: "heatmap"; - zmin: number; - zmid: number; - zmax: number; - colorscale: [number, string][]; - name: string; - hovertemplate: HeatmapCaptions["tooltipText"]; - colorbar: { - thickness: number; - outlinewidth: number; - tickcolor: string; - tickfont: FontSettings; - }; - y: HeatmapData["yAxisPoints"]; - x: HeatmapData["xAxisPoints"]; -} - -export interface DisplaySettings { - displayModeBar: boolean; - responsive?: boolean; -} - -export interface PartitionTimeStampToData { - [timeSeriesDates: string]: { - [NormalizedThroughput: string]: number; - }; -} diff --git a/test/testExplorer/TestExplorer.ts b/test/testExplorer/TestExplorer.ts index bea2c612a..b6d313a08 100644 --- a/test/testExplorer/TestExplorer.ts +++ b/test/testExplorer/TestExplorer.ts @@ -89,7 +89,7 @@ const initTestExplorer = async (): Promise => { iframe.setAttribute("data-test", "DataExplorerFrame"); iframe.classList.add("iframe"); iframe.title = "explorer"; - iframe.src = iframeSrc; + iframe.src = iframeSrc; // CodeQL [SM03712] Not used in production, only for testing purposes document.body.appendChild(iframe); }; diff --git a/webpack.config.js b/webpack.config.js index 917d4ca87..38ace4c1f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -106,23 +106,21 @@ module.exports = function (_env = {}, argv = {}) { typescriptRule.use[0].options.compilerOptions = { target: "ES2018" }; } - const plugins = [ - new CleanWebpackPlugin(), - new webpack.ProvidePlugin({ - process: "process/browser", - Buffer: ["buffer", "Buffer"], - }), - new CreateFileWebpack({ - path: "./dist", - fileName: "version.txt", - content: `${gitSha.trim()} ${new Date().toUTCString()}`, - }), - // TODO Enable when @nteract once removed - // ./node_modules/@nteract/markdown/node_modules/@nteract/presentational-components/lib/index.js line 63 breaks this with physical file Icon.js referred to as icon.js - // new CaseSensitivePathsPlugin(), - new MiniCssExtractPlugin({ - filename: "[name].[contenthash].css", - }), + const entry = { + main: "./src/Main.tsx", + index: "./src/Index.tsx", + quickstart: "./src/quickstart.ts", + hostedExplorer: "./src/HostedExplorer.tsx", + terminal: "./src/Terminal/index.ts", + cellOutputViewer: "./src/CellOutputViewer/CellOutputViewer.tsx", + notebookViewer: "./src/NotebookViewer/NotebookViewer.tsx", + galleryViewer: "./src/GalleryViewer/GalleryViewer.tsx", + selfServe: "./src/SelfServe/SelfServe.tsx", + connectToGitHub: "./src/GitHub/GitHubConnector.ts", + ...(mode !== "production" && { testExplorer: "./test/testExplorer/TestExplorer.ts" }), + }; + + const htmlWebpackPlugins = [ new HtmlWebpackPlugin({ filename: "explorer.html", template: "src/explorer.html", @@ -148,16 +146,6 @@ module.exports = function (_env = {}, argv = {}) { template: "src/hostedExplorer.html", chunks: ["hostedExplorer"], }), - new HtmlWebpackPlugin({ - filename: "testExplorer.html", - template: "test/testExplorer/testExplorer.html", - chunks: ["testExplorer"], - }), - new HtmlWebpackPlugin({ - filename: "Heatmap.html", - template: "src/Controls/Heatmap/Heatmap.html", - chunks: ["heatmap"], - }), new HtmlWebpackPlugin({ filename: "cellOutputViewer.html", template: "src/CellOutputViewer/cellOutputViewer.html", @@ -183,6 +171,35 @@ module.exports = function (_env = {}, argv = {}) { template: "src/SelfServe/selfServe.html", chunks: ["selfServe"], }), + ...(mode !== "production" + ? [ + new HtmlWebpackPlugin({ + filename: "testExplorer.html", + template: "test/testExplorer/testExplorer.html", + chunks: ["testExplorer"], + }), + ] + : []), + ]; + + const plugins = [ + new CleanWebpackPlugin(), + new webpack.ProvidePlugin({ + process: "process/browser", + Buffer: ["buffer", "Buffer"], + }), + new CreateFileWebpack({ + path: "./dist", + fileName: "version.txt", + content: `${gitSha.trim()} ${new Date().toUTCString()}`, + }), + // TODO Enable when @nteract once removed + // ./node_modules/@nteract/markdown/node_modules/@nteract/presentational-components/lib/index.js line 63 breaks this with physical file Icon.js referred to as icon.js + // new CaseSensitivePathsPlugin(), + new MiniCssExtractPlugin({ + filename: "[name].[contenthash].css", + }), + ...htmlWebpackPlugins, new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/cellOutputViewer/]), new HTMLInlineCSSWebpackPlugin({ filter: (fileName) => fileName.includes("cellOutputViewer"), @@ -205,20 +222,7 @@ module.exports = function (_env = {}, argv = {}) { return { mode: mode, - entry: { - main: "./src/Main.tsx", - index: "./src/Index.tsx", - quickstart: "./src/quickstart.ts", - hostedExplorer: "./src/HostedExplorer.tsx", - testExplorer: "./test/testExplorer/TestExplorer.ts", - heatmap: "./src/Controls/Heatmap/Heatmap.ts", - terminal: "./src/Terminal/index.ts", - cellOutputViewer: "./src/CellOutputViewer/CellOutputViewer.tsx", - notebookViewer: "./src/NotebookViewer/NotebookViewer.tsx", - galleryViewer: "./src/GalleryViewer/GalleryViewer.tsx", - selfServe: "./src/SelfServe/SelfServe.tsx", - connectToGitHub: "./src/GitHub/GitHubConnector.ts", - }, + entry: entry, output: { chunkFilename: "[name].[chunkhash:6].js", filename: "[name].[chunkhash:6].js",