Forum Discussion
Issue Configuring Scheduled Refresh in Power BI Service RELATIVE PATH
Dear Power BI Community Members,
I've encountered a challenge while configuring the data source in the Power BI Service. When publishing my dashboard, I come across the following error message:
"Unable to schedule refresh for this semantic model because the following data sources currently do not support refresh: Data source for Query1."
I've identified that this error occurs in a specific Power Query step:
= Table.AddColumn(#"Filtered Rows", "Customize.1",
each try Json.Document(
Web.Contents("https://graph.microsoft.com/v1.0/planner/tasks/"& [value.id] &
"/details" )
)
I noticed that I can fix this error by using RelativePath, as follows:
= Table.AddColumn(#"Filtered Rows", "Customize.1", each try Json.Document(Web.Contents("https://graph.microsoft.com/v1.0/planner/tasks/",[RelativePath = [value.id] & "/details" ])) otherwise null )
However, a new issue arises since I am using Microsoft Graph to obtain Planner data. For this, I use a custom connector for authentication in the Power BI Service "Online."
If I add a new step to my dashboard adjusting only the connector as follows:
= Table.AddColumn(#"Filtered Rows", "Customize.1", each try PlannerTasks.Contents("https://graph.microsoft.com/v1.0/planner/tasks/"& [value.id] & "/details" ) )
I can access the data, but I return to the error message upon publishing:
"Unable to schedule refresh for this semantic model because the following data sources currently do not support refresh: Data source for Query1."
Thus, I believe the solution appears to be using RelativePath in the same way as in Web.Contents, as shown in the model below:
= Table.AddColumn(
#"Removed Other Columns",
"Customize",
each PlannerTasks.Contents(
"https://graph.microsoft.com/v1.0/planner/tasks/",
[RelativePath = [value.id] & "/details"]
)
)
However, this approach generates the following error in the column:
"Expression.Error: 2 arguments were passed to a function that expected 1. Details: Pattern= Arguments=[List]"
I'm unsure if there's another function besides RelativePath that could help resolve this issue, or if the problem lies in the connector potentially not supporting this functionality.
I appreciate in advance any guidance, suggestions, or shared experiences from the community. I'm eager to overcome this obstacle and enhance my use of Power BI.
If adjustments to the connector code are necessary to enable RelativePath, here is the current connector code for reference and potential review:
section GraphApiConnector;
//
// OAuth configuration settings
//
// TODO: set AAD client ID and client secret value in the idsecret.json file
client_secret = Json.Document(Extension.Contents("idsecret.json"))[client_secret];
client_id = Json.Document (Extension.Contents("idsecret.json"))[client_id];
redirect_uri = "https://oauth.powerbi.com/views/oauthredirect.html";
token_uri = "https://login.microsoftonline.com/organizations/oauth2/v2.0/token";
authorize_uri = "https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize";
logout_uri = "https://login.microsoftonline.com/logout.srf";
windowWidth = 720;
windowHeight = 1024;
// The "offline_access" scope is required to receive a refresh token value. It is added
// separately from the Graph scopes. Please see
// https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#offline_access
//
// For more information on available Graph scopes, please see
// https://developer.microsoft.com/en-us/graph/docs/authorization/permission_scopes
scope_prefix = "https://graph.microsoft.com/";
scopes = {
"User.ReadWrite",
"Group.Read.All"
};
// Exported function
Planner.Contents = (url as text) =>
let
content = Web.Contents(url),
json = Json.Document(content)
in
json;
[DataSource.Kind = "PlannerTasks", Publish = "PlannerTasks.UI"]
shared PlannerTasks.Contents = Value.ReplaceType(Planner.Contents, type function (url as Uri.Type) as any);
//
// Data Source definition
//
PlannerTasks = [
TestConnection = (dataSourcePath) => {"PlannerTasks.Contents", dataSourcePath},
Authentication = [
OAuth = [
StartLogin=StartLogin,
FinishLogin=FinishLogin,
Refresh=Refresh,
Logout=Logout
]
],
Label = Extension.LoadString("DataSourceLabel")
];
//
// UI Export definition
//
PlannerTasks.UI = [
Beta = true,
ButtonText = { "PlannerTasks.Contents", "Connect to Graph API" },
SourceImage = GraphApiConnector.Icons,
SourceTypeImage = GraphApiConnector.Icons
];
GraphApiConnector.Icons = [
Icon16 = { Extension.Contents("graphapi16.png"), Extension.Contents("graphapi20.png"), Extension.Contents("graphapi24.png"), Extension.Contents("graphapi32.png") },
Icon32 = { Extension.Contents("graphapi32.png"), Extension.Contents("graphapi40.png"), Extension.Contents("graphapi48.png"), Extension.Contents("graphapi64.png") }
];
//
// OAuth implementation
//
// See the following links for more details on AAD/Graph OAuth:
// * https://docs.microsoft.com/en-us/azure/active-directory/active-directory-protocols-oauth-code
// * https://graph.microsoft.io/en-us/docs/authorization/app_authorization
//
// StartLogin builds a record containing the information needed for the client
// to initiate an OAuth flow. Note for the AAD flow, the display parameter is
// not used.
//
// resourceUrl: Derived from the required arguments to the data source function
// and is used when the OAuth flow requires a specific resource to
// be passed in, or the authorization URL is calculated (i.e. when
// the tenant name/ID is included in the URL). In this example, we
// are hardcoding the use of the "common" tenant, as specified by
// the 'authorize_uri' variable.
// state: Client state value we pass through to the service.
// display: Used by certain OAuth services to display information to the
// user.
//
// Returns a record containing the following fields:
// LoginUri: The full URI to use to initiate the OAuth flow dialog.
// CallbackUri: The return_uri value. The client will consider the OAuth
// flow complete when it receives a redirect to this URI. This
// generally needs to match the return_uri value that was
// registered for your application/client.
// WindowHeight: Suggested OAuth window height (in pixels).
// WindowWidth: Suggested OAuth window width (in pixels).
// Context: Optional context value that will be passed in to the FinishLogin
// function once the redirect_uri is reached.
//
StartLogin = (resourceUrl, state, display) =>
let
authorizeUrl = authorize_uri & "?" & Uri.BuildQueryString([
client_id = client_id,
client_secret=client_secret,
redirect_uri = redirect_uri,
state = state,
scope = "offline_access " & GetScopeString(scopes, scope_prefix),
response_type = "code",
response_mode = "query",
login = "login"
])
in
[
LoginUri = authorizeUrl,
CallbackUri = redirect_uri,
WindowHeight = 720,
WindowWidth = 1024,
Context = null
];
// FinishLogin is called when the OAuth flow reaches the specified redirect_uri.
// Note for the AAD flow, the context and state parameters are not used.
//
// context: The value of the Context field returned by StartLogin. Use this to
// pass along information derived during the StartLogin call (such as
// tenant ID)
// callbackUri: The callbackUri containing the authorization_code from the service.
// state: State information that was specified during the call to StartLogin.
FinishLogin = (context, callbackUri, state) =>
let
// parse the full callbackUri, and extract the Query string
parts = Uri.Parts(callbackUri)[Query],
// if the query string contains an "error" field, raise an error
// otherwise call TokenMethod to exchange our code for an access_token
result = if (Record.HasFields(parts, {"error", "error_description"})) then
error Error.Record(parts[error], parts[error_description], parts)
else
TokenMethod("authorization_code", "code", parts[code])
in
result;
// Called when the access_token has expired, and a refresh_token is available.
//
Refresh = (resourceUrl, refresh_token) => TokenMethod("refresh_token", "refresh_token", refresh_token);
Logout = (token) => logout_uri;
// grantType: Maps to the "grant_type" query parameter.
// tokenField: The name of the query parameter to pass in the code.
// code: Is the actual code (authorization_code or refresh_token) to send to the service.
TokenMethod = (grantType, tokenField, code) =>
let
queryString = [
client_id = client_id,
client_secret=client_secret,
scope = "offline_access " & GetScopeString(scopes, scope_prefix),
grant_type = grantType,
redirect_uri = redirect_uri
],
queryWithCode = Record.AddField(queryString, tokenField, code),
tokenResponse = Web.Contents(token_uri, [
Content = Text.ToBinary(Uri.BuildQueryString(queryWithCode)),
Headers = [
#"Content-type" = "application/x-www-form-urlencoded",
#"Accept" = "application/json"
],
ManualStatusHandling = {400}
]),
body = Json.Document(tokenResponse),
result = if (Record.HasFields(body, {"error", "error_description"})) then
error Error.Record(body[error], body[error_description], body)
else
body
in
result;
//
// Helper Functions
//
Value.IfNull = (a, b) => if a <> null then a else b;
GetScopeString = (scopes as list, optional scopePrefix as text) as text =>
let
prefix = Value.IfNull(scopePrefix, ""),
addPrefix = List.Transform(scopes, each prefix & _),
asText = Text.Combine(addPrefix, " ")
in
asText;
2 Replies
- Greg_DecklerCommunity Champion
Alph4 Seems like you need to update your function definition to include an optional relative path parameter:
Planner.Contents = (url as text) =>- Alph4Frequent Visitor
Should I make this adjustment in my M code or Power Query or in the custom connector?