Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
denizs committed Oct 10, 2024
0 parents commit 612278e
Show file tree
Hide file tree
Showing 47 changed files with 1,559 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[core]
autocrlf = false
27 changes: 27 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Attach Custom Connector as release asset

on:
release:
types: [published]

jobs:
build-and-attach:
runs-on: windows-latest

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up MSBuild
uses: microsoft/setup-msbuild@v1

- name: Build .mez file
run: |
msbuild enlyze.pq.proj /p:Configuration=Release
- name: Upload `.mez` as release asset
uses: softprops/action-gh-release@v1
with:
files: bin/AnyCPU/Release/enlyze-powerbi.mez
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.DS_Store
bin/
14 changes: 14 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "powerquery",
"request": "launch",
"name": "Evaluate power query file.",
"program": "${workspaceFolder}/${command:AskForPowerQueryFileName}"
}
]
}
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"powerquery.sdk.defaultQueryFile": "${workspaceFolder}\\enlyze.query.pq",
"powerquery.sdk.defaultExtension": "${workspaceFolder}\\bin\\AnyCPU\\Debug\\enlyze-powerbi.mez",
"powerquery.general.mode": "SDK"
}
22 changes: 22 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Build and deploy",
"type": "shell",
"command": "powershell.exe",
"args": [
"-ExecutionPolicy",
"Bypass",
"-File",
"${workspaceFolder}/push-extension.ps1"
],
"presentation": {
"reveal": "always",
"panel": "new"
},
"group": "build",
"problemMatcher": []
}
]
}
70 changes: 70 additions & 0 deletions ApiClient.pqm
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
let
BaseUrl = "https://app.enlyze.com/api/v2",
CommonHeaders = [
#"Content-Type" = "application/json",
#"user-agent" = "enlyze-powerbi/1.0.0"
],
CreateHeaders = (apiToken as text) as record =>
Record.Combine({CommonHeaders, [#"Authorization" = "Bearer " & apiToken]}),
FetchPage = (apiPath as text, cursor as nullable text, optional queryParams as nullable record) =>
let
apiUrl = BaseUrl & apiPath,
apiToken = Extension.CurrentCredential()[Key],
headers = CreateHeaders(apiToken),
combinedQueryParams =
if queryParams <> null then
Record.Combine({queryParams, [cursor = cursor]})
else
[cursor = cursor],
fieldNames = Record.FieldNames(combinedQueryParams),
nullValueFields = List.Select(fieldNames, each Record.Field(combinedQueryParams, _) = null),
queryParamsNonNull = Record.RemoveFields(combinedQueryParams, nullValueFields),
queryString = Uri.BuildQueryString(queryParamsNonNull),
apiUrlWithQueryParams = if Text.Length(queryString) > 0 then apiUrl & "?" & queryString else apiUrl,
parsedResponse = Json.Document(Web.Contents(apiUrlWithQueryParams, [Headers = headers]))
in
parsedResponse,
FetchPaginated = (apiPath as text, cursor as nullable text, optional queryParams as nullable record) as list =>
let
currentPage = FetchPage(apiPath, cursor, queryParams),
nextCursor = currentPage[metadata][next_cursor],
data = currentPage[data],
remainingData = if nextCursor = null then {} else @FetchPaginated(apiPath, nextCursor, queryParams)
in
List.Combine({data, remainingData}),
PostRequestPage = (apiPath as text, body as record, cursor as nullable text) as record =>
let
bodyWithCursor = if cursor <> null then Record.Combine({body, [cursor = cursor]}) else body,
url = BaseUrl & apiPath,
apiToken = Extension.CurrentCredential()[Key],
headers = CreateHeaders(apiToken),
response = Web.Contents(
url,
[
Headers = headers,
Content = Json.FromValue(bodyWithCursor),
ManualStatusHandling = {400, 401, 403, 404, 422, 500}
]
),
statusCode = Value.Metadata(response)[Response.Status],
parsedResponse =
if statusCode = 200 then
Json.Document(response)
else
error "HTTP Error: " & Text.From(statusCode) & ". Response body: " & Text.FromBinary(response)
in
parsedResponse,
PaginatedPostRequest = (apiPath as text, body as record, optional cursor as nullable text) as list =>
let
currentPage = PostRequestPage(apiPath, body, cursor),
dataMaybeRecord = currentPage[data],
data = if Type.Is(Value.Type(dataMaybeRecord), List.Type) then dataMaybeRecord else {dataMaybeRecord},
nextCursor = currentPage[metadata][next_cursor],
remainingData = if nextCursor = null then {} else @PaginatedPostRequest(apiPath, body, nextCursor)
in
List.Combine({data, remainingData})
in
[
FetchPaginated = FetchPaginated,
PaginatedPostRequest = PaginatedPostRequest
]
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# ENLYZE Power BI Integration

The ENLYZE Power BI Integration enables users to pull in their production data into Power BI. The project is based on the [Power Query Connector Development SDK](https://github.com/microsoft/vscode-powerquery-sdk).

## Features

The ENLYZE Power BI Integration currently supports querying the following resources:

- Sites
- Machines
- Production Runs
- Products
- Downtimes
- Productivity Metrics for machines

## Installation

In order to get started with the ENLYZE Power BI Integration,

1. Download the `enlyze-powerbi.mez` file from the [Latest Release](/releases/latest/download/enlyze-powerbi.mez)
2. Follow the steps [described in the official documentation](https://learn.microsoft.com/en-us/power-bi/connect-data/desktop-connector-extensibility#custom-connectors).
3. Restart Power BI

## Fetching data

With Power BI open, click on get data:

![menu bar](docs/images/menu-bar.png)

Then, search for enlyze:

![connector selection](docs/images/connector-select.png)

Finally, select the dataset you want to query:

![dataset selection](docs/images/dataset-select.png)
Binary file added docs/images/connector-select.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/dataset-select.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/menu-bar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions downtimes/Downtimes.TableSchema.pqm
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
let
DowntimesTableSchema = type table [
uuid = text,
machine = text,
#"type" = text,
start = datetimezone,
end = nullable datetimezone,
updated_first_name = nullable text,
updated_last_name = nullable text,
updated_timestamp = nullable datetimezone,
reason_uuid = nullable text,
reason_name = nullable text,
reason_category = nullable text
]
in
DowntimesTableSchema
40 changes: 40 additions & 0 deletions downtimes/Downtimes.Transform.pqm
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
let
loadModule = (fileName as text) =>
let
binary = Extension.Contents(fileName), asText = Text.FromBinary(binary)
in
try
Expression.Evaluate(asText, #shared) catch (e) =>
error
[
Reason = "Extension.LoadModule Failure",
Message.Format = "Loading '#{0}' failed - '#{1}': '#{2}'",
Message.Parameters = {fileName, e[Reason], e[Message]},
Detail = [File = fileName, Error = e]
],
TableSchema = loadModule("Downtimes.TableSchema.pqm"),
Table.ChangeType = loadModule("Table.ChangeType.pqm"),
TimeseriesData = loadModule("TimeseriesData.pqm"),
TransformDowntimes = (downtimes as list) as table =>
let
downtimesTable = Table.FromList(downtimes, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
namedTable = Value.ReplaceMetadata(downtimesTable, Value.Metadata(downtimesTable) & [Name = "Downtimes"]),
expandedTable = Table.ExpandRecordColumn(
namedTable, "Column1", {"uuid", "machine", "comment", "type", "updated", "reason", "start", "end"}
),
expandedUpdated = Table.ExpandRecordColumn(
expandedTable,
"updated",
{"first_name", "last_name", "timestamp"},
{"updated_first_name", "updated_last_name", "updated_timestamp"}
),
expandedReason = Table.ExpandRecordColumn(
expandedUpdated,
"reason",
{"uuid", "name", "category"},
{"reason_uuid", "reason_name", "reason_category"}
)
in
Table.ChangeType(expandedReason, TableSchema)
in
[TransformDowntimes = TransformDowntimes]
136 changes: 136 additions & 0 deletions enlyze.pq
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
[Version = "1.0.0"]
section enlyze;

loadModule = (fileName as text) =>
let
binary = Extension.Contents(fileName), asText = Text.FromBinary(binary)
in
try
Expression.Evaluate(asText, #shared) catch (e) =>
error
[
Reason = "loadModule Failure",
Message.Format = "Loading '#{0}' failed - '#{1}': '#{2}'",
Message.Parameters = {fileName, e[Reason], e[Message]},
Detail = [File = fileName, Error = e]
];

Table.ToNavigationTable = loadModule("Table.ToNavigationTable.pqm");

FetchPaginated = loadModule("ApiClient.pqm")[FetchPaginated];
PaginatedPostRequest = loadModule("ApiClient.pqm")[PaginatedPostRequest];

TransformProductivityMetrics = loadModule("ProductivityMetrics.Transform.pqm")[TransformProductivityMetrics];
TransformProductionRuns = loadModule("ProductionRuns.Transform.pqm")[TransformProductionRuns];
TransformSites = loadModule("Sites.Transform.pqm")[TransformSites];
TransformMachines = loadModule("Machines.Transform.pqm")[TransformMachines];
TransformProducts = loadModule("Products.Transform.pqm")[TransformProducts];
TransformDowntimes = loadModule("Downtimes.Transform.pqm")[TransformDowntimes];
TransformTimeseriesData = loadModule("TimeseriesData.Transform.pqm")[TransformTimeseriesData];
TransformVariables = loadModule("Variables.Transform.pqm")[TransformVariables];

MachineProductivityMetrics = loadModule("MachineProductivityMetrics.pqm");
TimeseriesData = loadModule("TimeseriesData.pqm");

[DataSource.Kind = "enlyze", Publish = "enlyze.Publish"]
shared enlyze.Contents = () =>
let
NavTable = Table.ToNavigationTable(
#table(
{"Name", "Key", "Data", "ItemKind", "ItemName", "IsLeaf"},
{
{
"Downtimes",
"downtimes",
TransformDowntimes(FetchPaginated("/downtimes", null)),
"Table",
"Table",
true
},
{
"Production Runs",
"productionRuns",
TransformProductionRuns(FetchPaginated("/production-runs", null)),
"Table",
"Table",
true
},
{
"Machines",
"machines",
TransformMachines(FetchPaginated("/machines", null)),
"Table",
"Table",
true
},
{"Sites", "sites", TransformSites(FetchPaginated("/sites", null)), "Table", "Table", true},
{
"Products",
"products",
TransformProducts(FetchPaginated("/products", null)),
"Table",
"Table",
true
},
{
"Machine Productivity Metrics",
"machineProductivityMetrics",
MachineProductivityMetrics,
"Function",
"Function",
true
},
{
"Variables",
"Variables",
TransformVariables(FetchPaginated("/variables", null)),
"Table",
"Table",
true
},
{"Timeseries", "Timeseries", TimeseriesData, "Function", "Function", true}
}
),
{"Key"},
"Name",
"Data",
"ItemKind",
"ItemName",
"IsLeaf"
)
in
NavTable;

enlyze = [
Authentication = [
Key = [
Label = "ENLYZE API Key",
KeyLabel = "ENLYZE API Key"
]
],
Label = "ENLYZE"
];

enlyze.Publish = [
Beta = true,
Category = "Other",
ButtonText = {Extension.LoadString("ButtonTitle"), Extension.LoadString("ButtonHelp")},
LearnMoreUrl = "https://docs.enlyze.com/",
SourceImage = enlyze.Icons,
SourceTypeImage = enlyze.Icons
];

enlyze.Icons = [
Icon16 = {
Extension.Contents("ENLYZE16.png"),
Extension.Contents("ENLYZE20.png"),
Extension.Contents("ENLYZE24.png"),
Extension.Contents("ENLYZE32.png")
},
Icon32 = {
Extension.Contents("ENLYZE32.png"),
Extension.Contents("ENLYZE40.png"),
Extension.Contents("ENLYZE48.png"),
Extension.Contents("ENLYZE64.png")
}
];
Loading

0 comments on commit 612278e

Please sign in to comment.