# OEE example widget, Machine Table API example

**URL:** <https://community.tulip.co/t/oee-example-widget-machine-table-api-example/4791>\
**Category:** Developer\
**Tags:** custom-widgets\
**Created:** [March 18, 2022, 5:15pm UTC](https://community.tulip.co/t/oee-example-widget-machine-table-api-example/4791 "2022-03-18T17:15:16Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![Pete\_Hartnett](https://sea2.discourse-cdn.com/flex020/user_avatar/community.tulip.co/pete_hartnett/32/3678_2.png) [@Pete\_Hartnett](https://community.tulip.co/u/Pete_Hartnett)\
**Post date:** [March 18, 2022, 5:15pm UTC](https://community.tulip.co/t/oee-example-widget-machine-table-api-example/4791/1 "2022-03-18T17:15:16Z")

</div>

Hey All,

Stealing a super slick widget shamelessly to share with you. This is one of the widgets that came from our internal Hackathon with the feature.

_Note - While this widget was made by experienced developers, it hasnt gone through our normal code review process, use it as your own risk_

This is a good example because it shows how to hit machine tables through the api and a custom widget too. 2 things you will need to do to get it up and running:

1. Update the authenticationKey to match one for a bot in your instance
2. Adjust the url of the table it is looking at to match the machine in your instance you want oee insight on.

[This](https://community.tulip.co/t/getting-table-records-from-the-api-in-javascript/4790) post goes into more detail on the process of hitting the api in a custom widget ([Getting Table Records from the API in JavaScript](https://community.tulip.co/t/getting-table-records-from-the-api-in-javascript/4790))

Hope this helps,  
Pete

UPDATE: This json keeps failing to attach to this post, below is the contents of that json. just copy it to a text file and save it with the .json extension and you should be able to import it into your instance.

```auto
{
    "version": 1,
    "customWidget": {
        "_id": "Pi2bD6nQ9HaACekFS",
        "name": "Machine OEE Widget (sharable)",
        "props": [{
            "name": "Machine",
            "type": "machine",
            "requireWriteable": false,
            "id": "DjLysJs6PmaYAnRBn"
        }],
        "events": [],
        "html": "<!DOCTYPE html>\n<head>\n <script\n type=\"text/javascript\"\n src=\"https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js\"\n ></script>\n <script\n src=\"https://code.jquery.com/jquery-3.6.0.min.js\"\n integrity=\"sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=\"\n crossorigin=\"anonymous\"\n ></script>\n <script\n src=\"https://cdn.jsdelivr.net/npm/chart.js@3.7.1/dist/chart.min.js\"\n integrity=\"sha256-ErZ09KkZnzjpqcane4SCyyHsKAXMvID9/xwbl/Aq1pc=\"\n crossorigin=\"anonymous\"\n ></script>\n <script type=\"text/javascript\" src=\"./index.js\"></script>\n <link rel=\"stylesheet\" href=\"styles.css\">\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js\" integrity=\"sha512-qTXRIMyZIFb8iQcfjXWCO8+M5Tbc38Qi5WzdPOYZHIlZpzBHG3L3by84BBBOiRGiEb7KKtAOAs5qYdUiZiQNNQ==\" crossorigin=\"anonymous\" referrerpolicy=\"no-referrer\"></script>\n</head>\n\n<body>\n <div class=\"widget-container\">\n <div class=\"left-pannel\">\n <div class=\"headerWrapper\">\n <div class=\"calculationWrapper headerItem\">\n <h3>Performance</h3>\n <div class=\"bordered\">\n <h1 id=\"performanceCalculationValue\">--%</h1>\n </div>\n </div>\n <div class=\"calculationWrapper headerItem\">\n <h3>Quality</h3>\n <div class=\"bordered\">\n <h1 id=\"qualityCalculationValue\">--%</h1>\n </div>\n </div>\n <div class=\"calculationWrapper headerItem\">\n <h3>Availability</h3>\n <div class=\"bordered\">\n <h1 id=\"availabilityCalculationValue\">--%</h1>\n </div>\n </div>\n <div class=\"calculationWrapper headerItem\">\n <h3>OEE</h3>\n <div class=\"bordered\">\n <h1 id=\"oeeCalculationValue\">--%</h1>\n </div>\n </div>\n </div>\n\n <div class=\"chart-container\">\n <canvas id=\"myChart\"></canvas>\n </div>\n </div>\n\n <div class=\"right-pannel\">\n <div class=\"keyWrapper\">\n <h3>Key</h3>\n <div class=\"key bordered\">\n <div class=\"keyEntry\">\n <span class=\"keyColor a-loss\"></span>\n <p class=\"keyName\">Availability loss</p>\n </div>\n <div class=\"keyEntry\">\n <span class=\"keyColor p-loss\"></span>\n <p class=\"keyName\">Performance loss</p>\n </div>\n <div class=\"keyEntry\">\n <span class=\"keyColor q-loss\"></span>\n <p class=\"keyName\">Quality loss</p>\n </div>\n <div class=\"keyEntry\">\n <span class=\"keyColor ideal\"></span>\n <p class=\"keyName\">Ideal run time</p>\n </div>\n </div>\n </div>\n\n <div class=\"settings\">\n <section>\n <label for=\"time-range\">Time range:</label>\n <select name=\"Time Range\" id=\"time-range\">\n <option value=\"Last 24hrs\">Last 15 minutes</option>\n <option value=\"Last 60 minutes\">Last 60 minutes</option>\n <option value=\"Last 4 hours\">Last 4 hours</option>\n <option value=\"Last 24 hours\">Last 24 hours</option>\n <option value=\"Last 7 days\" selected>Last 7 days</option>\n <option value=\"Last 30 days\">Last 30 days</option>\n </select>\n </section>\n\n <section>\n <label for=\"time-interval\">Time interval:</label>\n <select name=\"Time Range\" id=\"time-interval\">\n <option value=\"1 minute\">1 minute</option>\n <option value=\"2 minutes\">2 minutes</option>\n <option value=\"5 minutes\">5 minutes</option>\n <option value=\"10 minutes\">10 minutes</option>\n <option value=\"60 minutes\">60 minutes</option>\n <option value=\"4 hours\">4 hours</option>\n <option value=\"1 day\" selected>1 day</option>\n <option value=\"1 week\">1 week</option>\n </select>\n </section>\n </div>\n </div>\n </div>\n</body>\n",
        "javascript": "const state = {\n machineID: \"NEProzkcZnfakytgB\",\n bucketUnit: \"day\",\n};\n\nif (typeof getValue !== \"undefined\") {\n getValue(\"Machine\", (machine) => {\n if (machine == null) {\n return;\n }\n state.machineID = machine.id;\n handleMachineChange(machine.id);\n });\n}\n\nfunction toPercent(n) {\n return `${(n * 100).toFixed(2)}%`;\n}\n\nfunction now() {\n // Fixes time for the demo\n return moment(\"2022-02-15T17:00:00\");\n}\n\nasync function setCurrentCalculationValues(\n performance,\n availability,\n quality,\n oee\n) {\n document.getElementById(\"performanceCalculationValue\").innerHTML = toPercent(\n performance\n );\n document.getElementById(\"availabilityCalculationValue\").innerHTML = toPercent(\n availability\n );\n document.getElementById(\"qualityCalculationValue\").innerHTML = toPercent(\n quality\n );\n document.getElementById(\"oeeCalculationValue\").innerHTML = toPercent(oee);\n}\n\nasync function fetchMachineData(machineID) {\n const authorizationBasic =\n \"Basic YXBp...\";\n\n let myHeaders = new Headers();\n myHeaders.append(\"Accept\", \"application/json\");\n myHeaders.append(\"Authorization\", authorizationBasic);\n myHeaders.append(\n \"Content-Type\",\n \"application/x-www-form-urlencoded; charset=UTF-8\"\n );\n\n const myInit = {\n method: \"GET\",\n headers: myHeaders,\n mode: \"cors\",\n cache: \"default\",\n };\n\n let myRequest = new Request(\n `https://customwidgets.tulip.co/api/v3/tables/YocNLtbgCtcdBRSdG/records?sortOptions=%5B%7B%22sortBy%22%3A%22_sequenceNumber%22%2C%22sortDir%22%3A%22asc%22%7D%5D&filters=%5B%7B%22field%22%3A%22mm_machine_id%22%2C%22arg%22%3A%22${machineID}%22%2C%22functionType%22%3A%22equal%22%7D%5D&filterAggregator=%22all%22&limit=100&offset=0`\n );\n\n const response = await fetch(myRequest, myInit);\n const machineData = await response.json();\n return machineData;\n}\n\nconst UPTIME = [\"running_machine_state\"];\n\nclass MetricsBuilder {\n constructor(rawMachineData) {\n this.cleanUp(rawMachineData);\n this.startTime = now().subtract(8, \"days\"); // now().subtract(10,'minutes')\n this.endTime = now();\n this.bucketSize = moment.duration(1, \"days\");\n this.machineData = rawMachineData;\n this.updateTimeBuckets();\n }\n\n cleanUp(rawMachineData) {\n _.map(this.machineData, (machineRow) => {\n _.update(machineRow, \"mm_start_time\", (obj) => {\n return moment(obj);\n });\n _.update(machineRow, \"mm_end_time\", (obj) => {\n return moment(obj);\n });\n _.update(machineRow, \"_createdAt\", (obj) => {\n return moment(obj);\n });\n _.update(machineRow, \"_updatedAt\", (obj) => {\n return moment(obj);\n });\n });\n }\n\n updateTimeRange(startTime, endTime) {\n this.startTime = startTime;\n this.endTime = endTime;\n this.updateTimeBuckets();\n }\n\n updateBucketSize(val, unit) {\n this.bucketSize = moment.duration(val, unit);\n this.updateTimeBuckets();\n }\n\n updateTimeBuckets() {\n const rawBuckets = _.range(this.startTime, this.endTime, this.bucketSize);\n this.buckets = _.map(rawBuckets, (bucket) => {\n return moment(bucket).format();\n });\n this.splitMachineStatesOverBuckets();\n }\n\n splitMachineStateOverBucket(machineRow) {\n const bucketBoundaries = _.zip(\n _.slice(this.buckets, 0, this.buckets.length - 1),\n _.slice(this.buckets, 1)\n );\n const newRows = [machineRow];\n // TODO: make this more efficient\n _.each(bucketBoundaries, (bucketBoundary) => {\n const [start, end] = bucketBoundary;\n // the start of the machine row interval falls into the bucket, the end falls outside of the bucket\n if (\n !moment(machineRow.mm_start_time).isBefore(start) &&\n moment(machineRow.mm_start_time).isBefore(end) &&\n moment(machineRow.mm_end_time).isAfter(end)\n ) {\n // make a copy of the row\n const machineRowCopy = _.cloneDeep(machineRow);\n // set the end time of the original interval to the end of the bucket\n machineRow.mm_end_time = moment(end);\n machineRow.mm_duration =\n moment(machineRow.mm_end_time).diff(machineRow.mm_start_time) / 1000;\n // set the start of the new interval to the end of the bucket\n machineRowCopy.mm_start_time = moment(end);\n machineRowCopy.mm_duration =\n moment(machineRowCopy.mm_end_time).diff(\n machineRowCopy.mm_start_time\n ) / 1000;\n newRows.push(machineRowCopy);\n }\n });\n return newRows;\n }\n\n splitMachineStatesOverBuckets() {\n for (let i = 0; i < this.machineData.length; i++) {\n // split the current row into what falls into the bucket and what falls outside the bucket\n const newRows = this.splitMachineStateOverBucket(this.machineData[i]);\n // delete the current row and insert the new rows\n // syntax: splice(start, deleteCount, item1, item2, itemN)\n this.machineData.splice(i, 1, ...newRows);\n // now we resume the loop at the next row, which may be one of the rows we inserted, since\n // that new row may also need to be split up further\n }\n }\n\n bucketMachineRows() {\n return _.chain(this.machineData)\n .groupBy((machineRow) => {\n const bucketBoundaries = _.zip(\n _.slice(this.buckets, 0, this.buckets.length - 1),\n _.slice(this.buckets, 1)\n );\n // TODO: make this more efficient\n return _.find(bucketBoundaries, (bucketBoundary) => {\n const [start, end] = bucketBoundary;\n // the machine row fits entirely into the bucket\n if (\n !moment(machineRow.mm_start_time).isBefore(start) &&\n moment(machineRow.mm_start_time).isBefore(end) &&\n !moment(machineRow.mm_end_time).isAfter(end)\n ) {\n return start;\n }\n });\n })\n .values()\n .value();\n }\n\n // Set of metrics for a single bucket\n\n calculateAvailabilityLossSingleBucket(machineRows, totalTime) {\n const downtime = _.chain(machineRows)\n .filter((machineRow) => {\n return !UPTIME.includes(machineRow.mm_machine_state);\n })\n .sumBy(\"mm_duration\")\n .value();\n return totalTime - moment.duration(downtime, \"seconds\");\n }\n\n calculatePerformanceLossSingleBucket(machineRows) {\n const perfLoss = _.chain(machineRows)\n .filter((machineRow) => {\n return (\n machineRow.mm_defect_count != null &&\n machineRow.mm_ideal_run_rate != null &&\n machineRow.mm_ideal_run_rate > 0\n );\n })\n .map((machineRow) => {\n return machineRow.mm_defect_count / machineRow.mm_ideal_run_rate;\n })\n .mean()\n .value();\n return moment.duration(perfLoss, \"hours\").asMilliseconds();\n }\n\n calculateQualityLossSingleBucket(machineRows) {\n const qualityLoss = _.chain(machineRows)\n .filter((machineRow) => {\n return (\n machineRow.mm_defect_count != null &&\n machineRow.mm_part_count != null &&\n machineRow.mm_part_count > 0\n );\n })\n .map((machineRow) => {\n return machineRow.mm_defect_count / machineRow.mm_part_count;\n })\n .mean()\n .value();\n return moment.duration(qualityLoss, \"hours\").asMilliseconds();\n }\n\n // Set of metrics over time\n\n calculateMetricOverTime(func) {\n const bucketedRows = this.bucketMachineRows();\n return _.map(bucketedRows, func);\n }\n\n calculateAvailabilityLossOverTime() {\n return this.calculateMetric(\n _.partial(this.calculateAvailabilityLossSingleBucket, _, this.bucketSize)\n );\n }\n\n calculatePerformanceLossOverTime() {\n return this.calculateMetric(this.calculatePerformanceLossSingleBucket);\n }\n\n calculateQualityLossOverTime() {\n return this.calculateMetric(this.calculateQualityLossSingleBucket);\n }\n\n // Set of metric for data as a whole\n\n calculateMetric(func) {\n const bucketedRows = this.bucketMachineRows();\n return _.map(bucketedRows, func);\n }\n\n calculateAvailability() {\n const totalTime = this.bucketSize * this.buckets.length;\n const avail =\n 1 -\n this.calculateAvailabilityLossSingleBucket(this.machineData, totalTime) /\n totalTime;\n\n return avail;\n }\n\n calculatePerformance() {\n const totalTime = this.bucketSize * this.buckets.length;\n const perf =\n 1 -\n this.calculatePerformanceLossSingleBucket(this.machineData) / totalTime;\n\n return perf;\n }\n\n calculateQuality() {\n const totalTime = this.bucketSize * this.buckets.length;\n const qual =\n 1 - this.calculateQualityLossSingleBucket(this.machineData) / totalTime;\n\n return qual;\n }\n\n calculateOEE(...metrics) {\n return _.reduce(\n metrics,\n (acc, item) => {\n return acc * item;\n },\n 1\n );\n }\n\n reportMetrics() {\n const p = this.calculatePerformance();\n const q = this.calculateQuality();\n const a = this.calculateAvailability();\n\n return [p, q, a, this.calculateOEE(p, q, a)];\n }\n\n reportMetricsOverTime() {\n const als = this.calculateAvailabilityLossOverTime();\n const pls = this.calculatePerformanceLossOverTime();\n const qls = this.calculateQualityLossOverTime();\n const dateFormat =\n {\n day: \"MMM DD\",\n week: \"MMM DD\",\n }[state.bucketUnit] || \"lll\";\n const times = _.map(this.buckets, (bucket) =>\n moment(bucket).format(dateFormat)\n );\n\n const zipped = _.zip(als, pls, qls);\n const vals = _.map(zipped, ([al, pl, ql]) => {\n return { al, pl, ql, idl: this.bucketSize - al - pl - ql };\n });\n\n return {\n times,\n vals,\n };\n }\n}\n\nlet metricsBuilder;\n\nasync function initWidget() {\n const rawMachineData = await fetchMachineData(state.machineID);\n metricsBuilder = new MetricsBuilder(rawMachineData);\n initChart(metricsBuilder.reportMetricsOverTime());\n}\n\nasync function refreshWidget() {\n updateChart(metricsBuilder.reportMetricsOverTime());\n setCurrentCalculationValues(...metricsBuilder.reportMetrics());\n}\n\nfunction formatBucket(x) {\n return x / 1000 / 3600;\n}\n\nfunction initChart(machineData) {\n const ctx = document.getElementById(\"myChart\").getContext(\"2d\");\n const mockMachineData = {\n times: [\"Feb 1\", \"Feb 2\", \"Feb 3\", \"Feb 4\", \"Feb 5\", \"Feb 6\", \"Feb 7\"],\n vals: [\n genFakeDataPoint(24),\n genFakeDataPoint(24),\n genFakeDataPoint(24),\n genFakeDataPoint(24),\n genFakeDataPoint(24),\n genFakeDataPoint(24),\n genFakeDataPoint(24),\n],\n };\n\n const dataToUse = machineData || mockMachineData;\n\n const labels = dataToUse.times;\n const data = {\n labels: labels,\n datasets: [\n {\n id: \"al\",\n label: \"Availability loss\",\n data: _.map(_.map(dataToUse.vals, \"al\"), formatBucket),\n backgroundColor: \"rgba(255, 206, 86, 0.5)\",\n borderColor: \"rgba(255, 206, 86, 1)\",\n borderWidth: 2,\n },\n {\n id: \"pl\",\n label: \"Performance loss\",\n data: _.map(_.map(dataToUse.vals, \"pl\"), formatBucket),\n backgroundColor: \"rgba(255, 159, 64, 0.5)\",\n borderColor: \"rgba(255, 159, 64, 1)\",\n borderWidth: 2,\n },\n {\n id: \"ql\",\n label: \"Quality loss\",\n data: _.map(_.map(dataToUse.vals, \"ql\"), formatBucket),\n backgroundColor: \"rgba(255, 99, 132, 0.5)\",\n borderColor: \"rgba(255, 99, 132, 1)\",\n borderWidth: 2,\n },\n {\n id: \"idl\",\n label: \"Ideal run time\",\n data: _.map(_.map(dataToUse.vals, \"idl\"), formatBucket),\n backgroundColor: \"rgba(75, 192, 192, 0.5)\",\n borderColor: \"rgba(75, 192, 192, 1)\",\n borderWidth: 2,\n },\n].reverse(),\n };\n const config = {\n type: \"bar\",\n data: data,\n options: {\n plugins: {\n title: {\n display: false,\n text: \"...\",\n },\n legend: {\n display: false,\n },\n },\n responsive: true,\n scales: {\n x: {\n stacked: true,\n },\n y: {\n stacked: true,\n },\n },\n },\n };\n\n const DATA_COUNT = 7;\n const NUMBER_CFG = { count: DATA_COUNT, min: -100, max: 100 };\n\n state.chart = new Chart(ctx, config);\n}\n\nfunction updateChart(machineData) {\n const fakeNewData = {\n times: [\"13:00\", \"14:00\", \"15:00\", \"16:00\"],\n vals: [\n genFakeDataPoint(60),\n genFakeDataPoint(60),\n genFakeDataPoint(60),\n genFakeDataPoint(60),\n],\n };\n const dataToUse = machineData || fakeNewData;\n state.chart.data.labels = dataToUse.times;\n state.chart.data.datasets.forEach((dataset) => {\n dataset.data = _.map(_.map(dataToUse.vals, dataset.id), formatBucket);\n });\n state.chart.update();\n}\n\nfunction genFakeDataPoint(total) {\n const seeds = _.range(4).map(() => Math.random());\n seeds[3] *= 2; // Boost OEE, let's be optimistic about these machines\n const coeff = total / _.sum(seeds);\n const al = Math.floor(seeds[0] * coeff);\n const pl = Math.floor(seeds[1] * coeff);\n const ql = Math.floor(seeds[2] * coeff);\n const idl = total - al - pl - ql;\n return { al, pl, ql, idl };\n}\n\nfunction handleTimeRangeChange(newVal) {\n const [_, val, unit] = newVal.split(\" \");\n metricsBuilder.updateTimeRange(now().subtract(val, unit), now());\n refreshWidget();\n}\n\nfunction handleIntervalChange(newVal) {\n const [val, unit] = newVal.split(\" \");\n state.bucketUnit = unit;\n metricsBuilder.updateBucketSize(val, unit);\n refreshWidget();\n}\n\nasync function handleMachineChange(newVal) {\n const rawMachineData = await fetchMachineData(state.machineID);\n metricsBuilder = new MetricsBuilder(rawMachineData);\n refreshWidget();\n}\n\n// TODO: properly handle instantiating metricsBuilder\nfunction ready() {\n if (typeof metricsBuilder !== \"undefined\") {\n setCurrentCalculationValues(...metricsBuilder.reportMetrics());\n } else {\n setTimeout(ready, 100);\n }\n}\n\n$(\"document\").ready(initWidget);\n$(\"document\").ready(ready);\n$(\"document\").ready(() => {\n const timeRange = document.getElementById(\"time-range\");\n const timeInterval = document.getElementById(\"time-interval\");\n timeRange.addEventListener(\"change\", (e) => {\n handleTimeRangeChange(e.target.value);\n });\n timeInterval.addEventListener(\"change\", (e) => {\n handleIntervalChange(e.target.value);\n });\n});\n",
        "defaultWidth": 800,
        "defaultHeight": 500,
        "createdAt": 1647623356947,
        "createdBy": "uCq5LQBjpcXife8SE",
        "updatedAt": 1647623415678,
        "updatedBy": "uCq5LQBjpcXife8SE",
        "description": "",
        "css": "body {\n\tbackground-color: white;\n}\n\nbody {\n font-family: \"Helvetica Neue\", \"Helvetica\", \"Arial\", sans-serif;\n}\n.headerWrapper {\n display: flex;\n flex-direction: row;\n align-content: space-around;\n}\n\n.headerItem {\n display: flex;\n width: 25%;\n padding-left: 2%;\n padding-right: 2%;\n text-align: center;\n}\n\n.keyWrapper {\n flex-direction: column;\n align-content: space-around;\n margin-bottom: 10px;\n}\n.key {\n flex-direction: column;\n padding: 10px;\n}\n\n.keyColor {\n display: flex;\n height: 15px;\n width: 15px;\n background-color: blue;\n margin-right: 5px;\n border-radius: 5px;\n border-width: 1px;\n border-color: black;\n border-style: solid;\n}\n\n.keyEntry {\n display: flex;\n flex-direction: row;\n align-items: center;\n padding: 0px;\n height: 25px;\n}\n\n.calculationWrapper {\n display: flex;\n flex-direction: column;\n}\n\n.bordered {\n border-width: 1px;\n border-color: black;\n border-radius: 5px;\n border-style: solid;\n}\n\n.left-pannel {\n width: 70%;\n}\n\n.right-pannel {\n width: 25%;\n border-left: gray solid 1px;\n padding-left: 20px;\n padding-right: 20px;\n}\n\n.widget-container {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n}\n\n.a-loss {\n background-color: rgba(255, 206, 86, 0.5);\n border: solid 1px rgba(255, 206, 86, 1);\n}\n\n.p-loss {\n background-color: rgba(255, 159, 64, 0.5);\n border: solid 1px rgba(255, 159, 64, 1);\n}\n\n.q-loss {\n background-color: rgba(255, 99, 132, 0.5);\n border: solid 1px rgba(255, 99, 132, 1);\n}\n\n.ideal {\n background-color: rgba(75, 192, 192, 0.5);\n border: solid 1px rgba(75, 192, 192, 1);\n}\n\nbutton.refresh {\n margin-top: 15px;\n}\n\nsection {\n flex-direction: column;\n display: flex;\n}\n\nsection label {\n margin-bottom: 5px;\n margin-top: 10px;\n}\n",
        "thirdPartyLibraries": ["lodash", "jQuery", "chartJS"],
        "icon": "timeline"
    }
}

```

[customWidget-Machine OEE Widget (sharable).json](https://community.tulip.co/uploads/short-url/9lTjHOidEtPgUJZWB3Pm24BJZds.json) (20.9 KB)

---

<div class="post-metadata">

**Author:** ![jjj](https://sea2.discourse-cdn.com/flex020/user_avatar/community.tulip.co/jjj/32/4542_2.png) [@jjj](https://community.tulip.co/u/jjj)\
**Post date:** [October 12, 2023, 7:15pm UTC](https://community.tulip.co/t/oee-example-widget-machine-table-api-example/4791/2 "2023-10-12T19:15:41Z")

</div>

Hey @Pete_Hartnett, could you resend me this Widget export? i think it’s not letting me import since it’s a different release.

---

<div class="post-metadata">

**Author:** ![sebme](https://avatars.discourse-cdn.com/v4/letter/s/439d5e/32.png) [@sebme](https://community.tulip.co/u/sebme)\
**Post date:** [August 19, 2024, 7:45am UTC](https://community.tulip.co/t/oee-example-widget-machine-table-api-example/4791/3 "2024-08-19T07:45:52Z")

</div>

@Pete_Hartnett : where do I get the table ID for the machines from? The machine ID does not seem to do the trick? When I monitor the fetch in the browser log I see it looks for a different id when loading the activity table of a machine. Is this a “hidden feature”?

---

<div class="post-metadata">

**Author:** ![sebme](https://avatars.discourse-cdn.com/v4/letter/s/439d5e/32.png) [@sebme](https://community.tulip.co/u/sebme)\
**Post date:** [August 19, 2024, 7:54am UTC](https://community.tulip.co/t/oee-example-widget-machine-table-api-example/4791/4 "2024-08-19T07:54:28Z")

</div>

What are you guys hiding this behind temporary session credentials???

---

<div class="post-metadata">

**Author:** ![thorsten.langner](https://avatars.discourse-cdn.com/v4/letter/t/4bbf92/32.png) [@thorsten.langner](https://community.tulip.co/u/thorsten.langner)\
**Post date:** [August 19, 2024, 11:36am UTC](https://community.tulip.co/t/oee-example-widget-machine-table-api-example/4791/5 "2024-08-19T11:36:53Z")

</div>

> [@sebme](#):
>
> @Pete_Hartnett : where do I get the table ID for the machines from? The machine ID does not seem to do the trick?

I Guess you get it loke so:

- Go on you machines Details Tab.
- Hit F12 (Dev-Tools)
- Go to “Network” tab and clear it…
- Change to “Activity” Tab from your machine…  
 → First or one of the first entries should be a random string wich is the id  
You find the ID in the request URL on the Headers Tab  
 ![image](https://us1.discourse-cdn.com/flex020/uploads/agilemfg/original/2X/c/caabc112718f34f16ed710e3c726dee9905fbf33.png)  
And in the “Preview”& “Request” Tabs as well (as “id:”)

---

<div class="post-metadata">

**Author:** ![sebme](https://avatars.discourse-cdn.com/v4/letter/s/439d5e/32.png) [@sebme](https://community.tulip.co/u/sebme)\
**Post date:** [August 19, 2024, 12:12pm UTC](https://community.tulip.co/t/oee-example-widget-machine-table-api-example/4791/6 "2024-08-19T12:12:38Z")

</div>

@thorsten.langner : yeah, but it is only accessible from the “/g/” tabel end-point which is barred behind the Tulip session auth variable, which you cannot access from the custom-widget sandbox nor by going through the standard API unless there is a “feature-flag” for this…

---

<div class="post-metadata">

**Author:** ![thorsten.langner](https://avatars.discourse-cdn.com/v4/letter/t/4bbf92/32.png) [@thorsten.langner](https://community.tulip.co/u/thorsten.langner)\
**Post date:** [August 19, 2024, 12:25pm UTC](https://community.tulip.co/t/oee-example-widget-machine-table-api-example/4791/7 "2024-08-19T12:25:19Z")

</div>

ah sorry… now I get your point
