Compare commits

...

12 Commits

Author SHA1 Message Date
Dogukan Karatas 0a4ae9340a Merge pull request #217 from specklesystems/dogukan/bump-ol2-version
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
fix: objectloader2 version update
2025-11-05 16:37:17 +01:00
Dogukan Karatas 92bcf4b5c0 bump ol2 version 2025-11-05 16:06:13 +01:00
Dogukan Karatas 2a22bbf0af Merge pull request #216 from specklesystems/dogukan/arrange-buttons
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
fix (visual): button rearrangements
2025-11-03 13:45:28 +01:00
Dogukan Karatas 7b5e5397b6 minor changes 2025-11-03 13:35:15 +01:00
Dogukan Karatas 24eeb44ff7 Merge pull request #215 from specklesystems/dogukan/direct-server-download
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
feat (data): direct server download
2025-10-30 20:41:55 +01:00
Dogukan Karatas b1f16c4005 modifies the download procedure 2025-10-30 17:14:29 +01:00
Jedd Morgan 2307d87735 fix version again (#212) 2025-10-20 17:12:09 +01:00
Jedd Morgan b80624396d Update deploy.yml (#211) 2025-10-20 16:55:42 +01:00
Oğuzhan Koral 098ef3d112 Bump viewer for proxy fix (#210)
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
2025-10-20 10:44:54 +03:00
Oğuzhan Koral 94fdc7a2c3 bump viewer (#209)
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
2025-10-16 17:33:36 +03:00
Dogukan Karatas 525857bd26 adds version id suffix (#207)
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
Co-authored-by: Oğuzhan Koral <45078678+oguzhankoral@users.noreply.github.com>
2025-10-09 22:24:40 +03:00
Dogukan Karatas 959bcaa671 added a env check (#208) 2025-10-09 22:22:03 +03:00
8 changed files with 147 additions and 124 deletions
+2 -2
View File
@@ -26,7 +26,7 @@ jobs:
run: |
TAG=${{ github.ref_name }}
if [[ "${{ github.ref }}" != refs/tags/* ]]; then
TAG="v3.0.99.${{ github.run_number }}"
TAG="v3.0.99"
fi
SEMVER="${TAG#v}"
FILE_VERSION=$(echo "$TAG" | sed -E 's/^v([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
@@ -81,7 +81,7 @@ jobs:
run: |
TAG=${{ github.ref_name }}
if [[ "${{ github.ref }}" != refs/tags/* ]]; then
TAG="v3.0.99.${{ github.run_number }}"
TAG="v3.0.99"
fi
SEMVER="${TAG#v}"
FILE_VERSION=$(echo "$TAG" | sed -E 's/^v([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
@@ -142,11 +142,14 @@
// get structured data
structuredData = GetStructuredData(singleModelUrl),
// add the model name as context
// add the model name as context - with version id if exists
result = Table.AddColumn(
structuredData,
"Source Model",
each modelName,
structuredData,
"Source Model",
each if versionId <> null then
Text.Combine({modelName, "-", versionId})
else
modelName,
type text
)
in
@@ -1,13 +1,12 @@
(url as text) as list =>
let
// Import required functions
GetModel = Extension.LoadFunction("GetModel.pqm"),
Parser = Extension.LoadFunction("Parser.pqm"),
GetUser = Extension.LoadFunction("GetUser.pqm"),
GetVersion = Extension.LoadFunction("GetVersion.pqm"),
GetWorkspace = Extension.LoadFunction("GetWorkspace.pqm"),
// the logic for importing functions from other files
// helper function to load .pqm modules dynamically
Extension.LoadFunction = (fileName as text) =>
let
binary = Extension.Contents(fileName),
@@ -24,7 +23,6 @@
Detail = [File = fileName, Error = e]
],
// Get required information
modelInfo = GetModel(url),
parsedUrl = Parser(url),
userInfo = GetUser(url),
@@ -33,136 +31,144 @@
connectorVersion = GetVersion(),
workspaceInfo = GetWorkspace(url),
// Function to check if Desktop Service is available
IsDesktopServiceAvailable = () =>
// attempts to exchange powerful token for weak token via desktop service
// returns [Success = true/false, Token = weak_token/null]
TryTokenExchange = () =>
try
let
PingResponse = Web.Contents(
"http://127.0.0.1:29364/ping",
tokenExchangeData = Json.FromValue([
PowerfulToken = apiKey,
Scopes = {"profile:read", "streams:read", "users:read"},
ProjectId = parsedUrl[projectId],
ServerUrl = parsedUrl[baseUrl]
]),
tokenExchangeResponse = Web.Contents(
"http://127.0.0.1:29364/auth/exchange-token",
[
Headers = [#"Method" = "GET"],
Headers = [
#"Content-Type" = "application/json",
#"Method" = "POST"
],
Content = tokenExchangeData,
ManualStatusHandling = {400, 401, 403, 404, 500, 502, 503, 504},
Timeout = #duration(0, 0, 0, 2) // 2 second timeout for ping
Timeout = #duration(0, 0, 0, 5)
]
),
StatusCode = Value.Metadata(PingResponse)[Response.Status]
StatusCode = Value.Metadata(tokenExchangeResponse)[Response.Status],
Result = if StatusCode >= 200 and StatusCode < 300 then
let
tokenExchangeJson = Json.Document(tokenExchangeResponse),
weakToken = tokenExchangeJson[token]
in
[Success = true, Token = weakToken]
else
[Success = false, Token = null]
in
StatusCode = 200
Result
otherwise
false,
[Success = false, Token = null],
// Function to use Desktop Service approach (only called if available)
UseDesktopService = () =>
// stores user info to desktop service for power bi visual consumption
// returns status code (or 0 on failure)
SendTelemetry = (token as text) =>
try
let
userInfoData = Json.FromValue([
Url = url,
Server = parsedUrl[baseUrl],
Email = userEmail,
ProjectId = parsedUrl[projectId],
RootObjectId = modelInfo[rootObjectId],
SourceApplication = modelInfo[sourceApplication],
Token = token,
Version = connectorVersion,
VersionId = parsedUrl[versionId],
WorkspaceId = workspaceInfo[workspaceId],
WorkspaceName = workspaceInfo[workspaceName],
WorkspaceLogo = workspaceInfo[workspaceLogo],
CanHideBranding = workspaceInfo[canHideBranding]
]),
userInfoResponse = Web.Contents(
"http://127.0.0.1:29364/store-user-info",
[
Headers = [
#"Content-Type" = "application/json",
#"Method" = "POST"
],
Content = userInfoData,
ManualStatusHandling = {400, 401, 403, 404, 500, 502, 503, 504},
Timeout = #duration(0, 0, 0, 3)
]
),
statusCode = Value.Metadata(userInfoResponse)[Response.Status]
in
statusCode
otherwise
0,
// downloads data directly from server without desktop service
DirectDownload = (token as text) =>
let
// exchange powerful token for weak token via ds
tokenExchangeData = Json.FromValue([
PowerfulToken = apiKey,
Scopes = {"profile:read", "streams:read", "users:read"},
ProjectId = parsedUrl[projectId],
ServerUrl = parsedUrl[baseUrl]
]),
tokenExchangeResponse = Web.Contents(
"http://127.0.0.1:29364/auth/exchange-token",
[
Headers = [
#"Content-Type" = "application/json",
#"Method" = "POST"
],
Content = tokenExchangeData,
ManualStatusHandling = {400, 401, 403, 404, 500}
]
),
tokenExchangeJson = Json.Document(tokenExchangeResponse),
weakToken = tokenExchangeJson[token],
// prepare request data with weak token
requestData = Json.FromValue([
Url = url,
Server = parsedUrl[baseUrl],
Email = userEmail,
ProjectId = parsedUrl[projectId],
RootObjectId = modelInfo[rootObjectId],
SourceApplication = modelInfo[sourceApplication],
Token = weakToken,
Version = connectorVersion,
VersionId = parsedUrl[versionId],
WorkspaceId = workspaceInfo[workspaceId],
WorkspaceName = workspaceInfo[workspaceName],
WorkspaceLogo = workspaceInfo[workspaceLogo],
CanHideBranding = workspaceInfo[canHideBranding]
]),
// Send request to local server
Response = Web.Contents(
"http://127.0.0.1:29364/download",
[
Headers = [
#"Content-Type" = "application/json",
#"Method" = "POST"
],
Content = requestData,
ManualStatusHandling = {400, 401, 403, 404, 500}
]
),
// Parse response
JsonResponse = Json.Document(Response)
in
JsonResponse,
// Function to fallback to direct JSON download from Speckle server
FallbackToDirectDownload = () =>
let
// Construct the direct object URL: {baseUrl}/objects/{projectId}/{rootObjectId}
objectUrl = Text.Combine({
parsedUrl[baseUrl],
"/objects/",
parsedUrl[projectId],
"/",
parsedUrl[baseUrl],
"/objects/",
parsedUrl[projectId],
"/",
modelInfo[rootObjectId]
}),
// Download JSON directly from Speckle server
Response = Web.Contents(
objectUrl,
[
Headers = [
#"Authorization" = "Bearer " & apiKey,
#"Authorization" = "Bearer " & token,
#"Accept" = "application/json"
],
ManualStatusHandling = {400, 401, 403, 404, 500, 502, 503, 504}
]
),
// Check response status
StatusCode = Value.Metadata(Response)[Response.Status],
// Parse JSON response if successful
JsonResponse = if StatusCode >= 200 and StatusCode < 300 then
Json.Document(Response)
else
error [
Reason = "DirectDownloadFailed",
Message = "Failed to download model data directly from Speckle server",
Message.Format = "Failed to download model data from Speckle server (Status: #{0})",
Message.Parameters = {Text.From(StatusCode)},
Detail = [
StatusCode = StatusCode,
StatusCode = StatusCode,
ObjectUrl = objectUrl,
ProjectId = parsedUrl[projectId],
RootObjectId = modelInfo[rootObjectId]
RootObjectId = modelInfo[rootObjectId],
UsedWeakToken = token <> apiKey
]
]
in
JsonResponse,
// Check Desktop Service availability and choose approach
DesktopServiceAvailable = IsDesktopServiceAvailable(),
FinalResult = if DesktopServiceAvailable then
UseDesktopService()
// try token exchange, use weak token if successful, otherwise use powerful token
// powerful token just for data connector, never stored in visual
TokenExchangeResult = TryTokenExchange(),
TokenToUse = if TokenExchangeResult[Success] then
TokenExchangeResult[Token]
else
FallbackToDirectDownload()
apiKey,
// send user info to desktop service
TelemetryStatusCode = SendTelemetry(TokenToUse),
// download data
FinalResult = if TelemetryStatusCode >= 0 then
DirectDownload(TokenToUse)
else
DirectDownload(TokenToUse)
in
FinalResult
FinalResult
+14 -14
View File
@@ -13,10 +13,10 @@
"@babel/runtime-corejs3": "^7.21.5",
"@headlessui/vue": "^1.7.13",
"@heroicons/vue": "^2.0.12",
"@speckle/objectloader2": "2.26.2",
"@speckle/objectloader2": "2.26.7",
"@speckle/tailwind-theme": "2.23.2",
"@speckle/ui-components": "2.23.2",
"@speckle/viewer": "2.26.1",
"@speckle/viewer": "2.26.5",
"color-interpolate": "^1.0.5",
"core-js": "^3.30.2",
"lodash": "^4.17.21",
@@ -3406,21 +3406,21 @@
"peer": true
},
"node_modules/@speckle/objectloader2": {
"version": "2.26.2",
"resolved": "https://registry.npmjs.org/@speckle/objectloader2/-/objectloader2-2.26.2.tgz",
"integrity": "sha512-sX0Mpi9h54CoWAl58YVCef4JSxWNnB+pFfTjo1XNfBEuyfwL6JBO8j2ho5OYLIyag4VZ1yXu/3MfmSIY4lMq3w==",
"version": "2.26.7",
"resolved": "https://registry.npmjs.org/@speckle/objectloader2/-/objectloader2-2.26.7.tgz",
"integrity": "sha512-bc3hp/83DBnyp2TOaVtKfqRaWz5UKcWouW281JcFcezTn/TZnB+FSF2csClBhOzrWSqmGVns0KfoJVfflETghQ==",
"license": "Apache-2.0",
"dependencies": {
"@speckle/shared": "^2.26.2"
"@speckle/shared": "^2.26.7"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@speckle/shared": {
"version": "2.26.2",
"resolved": "https://registry.npmjs.org/@speckle/shared/-/shared-2.26.2.tgz",
"integrity": "sha512-fvsq8J0riSNEPL9WaExzStl2qyUZIzQPOrFPDe/Biylkgv89GTAJlZdYZ4q1AqfaL3o9wYYQ8tKudjl+cAgFrQ==",
"version": "2.26.7",
"resolved": "https://registry.npmjs.org/@speckle/shared/-/shared-2.26.7.tgz",
"integrity": "sha512-3TJNmC1JFKvrAfnEDqX7A4P8V/P8M26T8+7sNRtsIMaINFAvKbBjTi7KGFzoAwQmzeXdbUsd2jXF4Kt94W3ZKQ==",
"license": "Apache-2.0",
"dependencies": {
"dayjs": "^1.11.13",
@@ -3545,13 +3545,13 @@
}
},
"node_modules/@speckle/viewer": {
"version": "2.26.1",
"resolved": "https://registry.npmjs.org/@speckle/viewer/-/viewer-2.26.1.tgz",
"integrity": "sha512-COqGbk+086GBRpctvvgvP/NhXbq1OeieRUIosmUnXswTq8b6G3PGd7FJXAPs2KI1uhQNda+igzql8sFurn7gNA==",
"version": "2.26.5",
"resolved": "https://registry.npmjs.org/@speckle/viewer/-/viewer-2.26.5.tgz",
"integrity": "sha512-kC/mKYVQZW9f+ek4fcYBEsSEo5fk/cvhBRPFpUzu974gNkaQJAJKWGCziB4U2WHai/twQXJS/yhyrCNOAVBnzQ==",
"license": "Apache-2.0",
"dependencies": {
"@speckle/objectloader2": "^2.26.1",
"@speckle/shared": "^2.26.1",
"@speckle/objectloader2": "^2.26.5",
"@speckle/shared": "^2.26.5",
"@types/flat": "^5.0.2",
"earcut": "3.0.1",
"flat": "^5.0.2",
+2 -2
View File
@@ -17,10 +17,10 @@
"@babel/runtime-corejs3": "^7.21.5",
"@headlessui/vue": "^1.7.13",
"@heroicons/vue": "^2.0.12",
"@speckle/objectloader2": "2.26.2",
"@speckle/objectloader2": "2.26.7",
"@speckle/tailwind-theme": "2.23.2",
"@speckle/ui-components": "2.23.2",
"@speckle/viewer": "2.26.1",
"@speckle/viewer": "2.26.5",
"color-interpolate": "^1.0.5",
"core-js": "^3.30.2",
"lodash": "^4.17.21",
@@ -29,7 +29,7 @@
<div class="flex items-center space-x-2">
<FormButton
v-if="visualStore.latestAvailableVersion && !visualStore.isConnectorUpToDate"
v-if="visualStore.latestAvailableVersion && !visualStore.isConnectorUpToDate && visualStore.isRunningInDesktop"
v-tippy="{
content: 'New connector version is available.<br>Click to download.',
allowHTML: true
@@ -64,7 +64,7 @@
field is needed for interactivity with other visuals.
</div>
<div v-if="visualStore.isNavbarHidden" class="fixed top-0 right-0 z-20">
<div v-if="visualStore.isNavbarHidden" class="fixed top-4 right-2 z-20">
<button
class="transition opacity-50 hover:opacity-100"
title="Show navbar"
@@ -165,7 +165,7 @@ onMounted(async () => {
// Set up event listener for object clicks from the FilteredSelectionExtension
viewerHandler.emitter.on('objectClicked', handleObjectClicked)
visualStore.setViewerEmitter(viewerHandler.emit)
})
@@ -41,8 +41,12 @@ export function useUpdateConnector() {
return new Date(b.Date).getTime() - new Date(a.Date).getTime()
})
versions.value = sortedVersions
const sanitizedVersion = sanitizeVersion(sortedVersions[0].Number)
latestAvailableVersion.value = { ...sortedVersions[0], Number: sanitizedVersion }
// Filter out prerelease versions
const stableVersions = sortedVersions.filter((v) => !v.Prerelease)
const latestVersion = stableVersions[0]
const sanitizedVersion = sanitizeVersion(latestVersion.Number)
latestAvailableVersion.value = { ...latestVersion, Number: sanitizedVersion }
visualStore.setLatestAvailableVersion(latestAvailableVersion.value)
}
@@ -111,6 +111,15 @@ export const useVisualStore = defineStore('visualStore', () => {
return false
})
// detecting the env to control the visibility of update button
// might use for different reasons in the future
const isRunningInDesktop = computed(() => {
// power bi hostEnv enum values:
// web = 1, desktop = 4
const hostEnv = host.value?.['hostEnv'] as number
return hostEnv === 4
})
/**
* Ideally one time set when onMounted of `ViewerWrapper.vue` component
* @param emit picky emit function to trigger events under `IViewerEvents` interface
@@ -559,6 +568,7 @@ export const useVisualStore = defineStore('visualStore', () => {
isZoomOnFilterActive,
latestAvailableVersion,
isConnectorUpToDate,
isRunningInDesktop,
commonError,
previousToggleState,
setCommonError,