private SiteProject CreateProject(string projectName, string projectDescription) { //ref: http://onlinehelp.tableau.com/current/api/rest_api/en-us/help.htm#REST/rest_api_ref.htm#Create_Project%3FTocPath%3DAPI%2520Reference%7C_____12 var sb = new StringBuilder(); var xmlWriter = XmlWriter.Create(sb, XmlHelper.XmlSettingsForWebRequests); xmlWriter.WriteStartElement("tsRequest"); xmlWriter.WriteStartElement("project"); xmlWriter.WriteAttributeString("name", projectName); xmlWriter.WriteAttributeString("description", projectDescription); xmlWriter.WriteEndElement(); //</project> xmlWriter.WriteEndElement(); // </tsRequest> xmlWriter.Close(); var xmlText = sb.ToString(); //Get the XML text out //Generate the MIME message //var mimeGenerator = new OnlineMimeXmlPayload(xmlText); //Create a web request var urlCreateProject = _onlineUrls.Url_CreateProject(_onlineSession); var webRequest = this.CreateLoggedInWebRequest(urlCreateProject, "POST"); TableauServerRequestBase.SendPostContents(webRequest, xmlText); //Get the response var xmlDoc = GetWebReponseLogErrors_AsXmlDoc(webRequest, "create project"); //Get all the workbook nodes var nsManager = XmlHelper.CreateTableauXmlNamespaceManager("iwsOnline"); var xNodeProject = xmlDoc.SelectSingleNode("//iwsOnline:project", nsManager); try { return(new SiteProject(xNodeProject)); } catch (Exception parseXml) { StatusLog.AddError("Create project, error parsing XML response " + parseXml.Message + "\r\n" + xNodeProject.InnerXml); return(null); } }
/// <summary> /// /// </summary> /// <param name="serverName"></param> public void ExecuteRequest() { var onlineUser = new List <SiteUser>(); int numberPages = 1; //Start with 1 page (we will get an updated value from server) //Get subsequent pages for (int thisPage = 1; thisPage <= numberPages; thisPage++) { try { ExecuteRequest_ForPage(onlineUser, thisPage, out numberPages); } catch (Exception exPageRequest) { StatusLog.AddError("Users list error during page request: " + exPageRequest.Message); } } _users = onlineUser; }
/// <summary> /// Sanity testing on whether the file being uploaded is worth uploading /// </summary> /// <param name="localFilePath"></param> /// <returns></returns> bool IsValidUploadFile(string localFilePath) { //Ignore temp files, since we know we don't want to upload them var fileExtension = Path.GetExtension(localFilePath).ToLower(); if ((fileExtension == ".tmp") || (fileExtension == ".temp")) { StatusLog.AddStatus("Ignoring temp file, " + localFilePath, -10); return(false); } //These are the only kinds of data sources we know about... if ((fileExtension != ".twb") && (fileExtension != ".twbx")) { StatusLog.AddError("File is not a data source: " + localFilePath); return(false); } return(true); }
/// <summary> /// Execute the request for Groups /// </summary> public void ExecuteRequest() { var siteGroups = new List <SiteGroup>(); int numberPages = 1; //Start with 1 page (we will get an updated value from server) //Get subsequent pages for (int thisPage = 1; thisPage <= numberPages; thisPage++) { try { _ExecuteRequest_ForPage(siteGroups, thisPage, out numberPages); } catch (Exception exPageRequest) { StatusLog.AddError("Groups error during page request: " + exPageRequest.Message); } } _groups = siteGroups; }
/* * /// <summary> * /// Constructor: Call when we want to query the Flows on behalf of an explicitly specified user * /// </summary> * /// <param name="onlineUrls"></param> * /// <param name="login"></param> * /// <param name="user"></param> * public DownloadFlowsList(TableauServerSignIn login, string userId, bool filterToOwnedBy, int maxNumberItems = int.MaxValue) : base(login) * { * //Sanity test * if(!RegExHelper.IsValidIdTableauContentId(userId)) * { * throw new Exception("1030-139: Invalid user ID"); * } * * _onlineUrls = login.ServerUrls; * _maxNumberItemsReturned = maxNumberItems; * _sort = DownloadFlowsList.Sort.NoSort; //[2019-10-29] This is currently the only option * _userIdForContentQuery = userId; * _filterToOwnedBy = filterToOwnedBy; * } */ /// <summary> /// /// </summary> /// <param name="serverName"></param> public void ExecuteRequest() { /* //Sanity check * if(string.IsNullOrWhiteSpace(_userIdForContentQuery)) * { * _onlineSession.StatusLog.AddError("User ID required to query flows"); * } */ var onlineFlows = new List <SiteFlow>(); int numberPages = 1; //Start with 1 page (we will get an updated value from server) //Get subsequent pages for (int thisPage = 1; thisPage <= numberPages; thisPage++) { try { ExecuteRequest_ForPage(onlineFlows, thisPage, out numberPages); } catch (Exception exPageRequest) { StatusLog.AddError("Flows error during page request: " + exPageRequest.Message); } //See if we already have anough items if (onlineFlows.Count == _maxNumberItemsReturned) { goto exit_success; } else if (onlineFlows.Count > _maxNumberItemsReturned) { //Remove any excess onlineFlows.RemoveRange(_maxNumberItemsReturned, (onlineFlows.Count - _maxNumberItemsReturned)); goto exit_success; } } exit_success: _flows = onlineFlows; }
/// <summary> /// Execute request for Workbook Views /// </summary> public void ExecuteRequest() { if (string.IsNullOrWhiteSpace(_userId)) { OnlineSession.StatusLog.AddError("User ID required to query workbooks"); } try { var urlQuery = _onlineUrls.Url_ViewsListForWorkbook(_workbookId, OnlineSession); var webRequest = CreateLoggedInWebRequest(urlQuery); webRequest.Method = "GET"; OnlineSession.StatusLog.AddStatus($"Web request: {urlQuery}", -10); var response = GetWebResponseLogErrors(webRequest, "get views list"); var xmlDoc = GetWebResponseAsXml(response); var nsManager = XmlHelper.CreateTableauXmlNamespaceManager("iwsOnline"); var workbooks = xmlDoc.SelectNodes("//iwsOnline:view", nsManager); _views = new List <SiteView>(); foreach (XmlNode itemXml in workbooks) { try { var ds = new SiteView(itemXml); _views.Add(ds); } catch { AppDiagnostics.Assert(false, "View parse error"); OnlineSession.StatusLog.AddError("Error parsing view: " + itemXml.InnerXml); } } } catch (Exception exPageRequest) { StatusLog.AddError($"Workbooks error during page request: {exPageRequest.Message}"); } }
/// <summary> /// Request the data from Online /// </summary> /// <param name="downloadMemberList"></param> /// <returns></returns> public bool ExecuteRequest(bool downloadMemberList = true) { var siteGroups = new List <SiteGroup>(); int numberPages = 1; //Start with 1 page (we will get an updated value from server) //Get subsequent pages for (int thisPage = 1; thisPage <= numberPages; thisPage++) { try { ExecuteRequest_ForPage(siteGroups, thisPage, downloadMemberList, out numberPages); } catch (Exception exPageRequest) { StatusLog.AddError("Groups error during page request: " + exPageRequest.Message); return(false); } } _groups = siteGroups; return(true); }
/// <summary> /// Execute the request for Site Views /// </summary> public void ExecuteRequest() { if (string.IsNullOrWhiteSpace(_userId)) { OnlineSession.StatusLog.AddError("User ID required to query workbooks"); } int numberPages = 1; //Start with 1 page (we will get an updated value from server) //Get subsequent pages _views = new List <SiteView>(); for (int thisPage = 1; thisPage <= numberPages; thisPage++) { try { _ExecuteRequest_ForPage(_views, thisPage, out numberPages); } catch (Exception exPageRequest) { StatusLog.AddError($"Workbooks error during page request: {exPageRequest.Message}"); } } }
/// <summary> /// After a file has been uploaded in chunks, we need to make a call to COMMIT the file to server as a published Data Source /// </summary> /// <param name="uploadSessionId"></param> /// <param name="publishedContentName"></param> private SiteDatasource FinalizePublish( string uploadSessionId, string publishedContentName, string publishedContentType, string projectId, CredentialManager.Credential dbCredentials) { //See definition: http://onlinehelp.tableau.com/current/api/rest_api/en-us/help.htm#REST/rest_api_ref.htm#Publish_Datasource%3FTocPath%3DAPI%2520Reference%7C_____29 var sb = new StringBuilder(); var xmlWriter = XmlWriter.Create(sb, XmlHelper.XmlSettingsForWebRequests); xmlWriter.WriteStartElement("tsRequest"); xmlWriter.WriteStartElement("datasource"); xmlWriter.WriteAttributeString("name", publishedContentName); //If we have an associated database credential, write it out if (dbCredentials != null) { CredentialXmlHelper.WriteCredential( xmlWriter, dbCredentials); } xmlWriter.WriteStartElement("project"); //<project> xmlWriter.WriteAttributeString("id", projectId); xmlWriter.WriteEndElement(); //</project> xmlWriter.WriteEndElement(); // </datasource> //Currently not supporting <connectionCredentials> xmlWriter.WriteEndElement(); // </tsRequest> xmlWriter.Close(); var xmlText = sb.ToString(); //Get the XML text out //Generate the MIME message var mimeGenerator = new MimeWriterXml(xmlText); //Create a web request to push the var urlFinalizeUpload = _onlineUrls.Url_FinalizeDataSourcePublish(_onlineSession, uploadSessionId, publishedContentType); //NOTE: The publish finalization step can take several minutes, because server needs to unpack the uploaded ZIP and file it away. // For this reason, we pass in a long timeout var webRequest = this.CreateAndSendMimeLoggedInRequest(urlFinalizeUpload, "POST", mimeGenerator, TableauServerWebClient.DefaultLongRequestTimeOutMs); var response = GetWebReponseLogErrors(webRequest, "finalize datasource publish"); using (response) { var xmlDoc = GetWebResponseAsXml(response); //Get all the datasource node from the response var nsManager = XmlHelper.CreateTableauXmlNamespaceManager("iwsOnline"); var dataSourceXml = xmlDoc.SelectSingleNode("//iwsOnline:datasource", nsManager); try { return(new SiteDatasource(dataSourceXml)); } catch (Exception parseXml) { StatusLog.AddError("Data source upload, error parsing XML response " + parseXml.Message + "\r\n" + dataSourceXml.InnerXml); return(null); } } }
/// <summary> /// Uploads the contents of a directory to server /// </summary> /// <param name="rootContentPath"></param> /// <param name="currentContentPath"></param> /// <param name="recurseDirectories"></param> private void UploadDirectoryToServer( string rootContentPath, string currentContentPath, ProjectFindCreateHelper projectsList, bool recurseDirectories, out int countSuccess, out int countFailure) { countSuccess = 0; countFailure = 0; //Look up the project name based on directory name, and creating a project on demand string projectName; if (rootContentPath == currentContentPath) //If we are in the root upload directory, then assume any content goes to the Default project { projectName = ""; //Default project } else { projectName = FileIOHelper.Undo_GenerateWindowsSafeFilename(Path.GetFileName(currentContentPath)); } //Start off with no project ID -- we'll look it up as needed string projectIdForUploads = null; //------------------------------------------------------------------------------------- //Upload the files from local directory to server //------------------------------------------------------------------------------------- foreach (var thisFilePath in Directory.GetFiles(currentContentPath)) { bool isValidUploadFile = IsValidUploadFile(thisFilePath); if (isValidUploadFile) { //If we don't yet have a project ID, then get one if (string.IsNullOrWhiteSpace(projectIdForUploads)) { projectIdForUploads = projectsList.GetProjectIdForUploads(projectName); } try { //See if there are any credentials we want to publish with the content var dbCredentialsIfAny = helper_DetermineContentCredential( Path.GetFileName(thisFilePath), projectName); //See what content specific settings there may be for this workbook var publishSettings = DatasourcePublishSettings.GetSettingsForSavedDatasource(thisFilePath); //Do the file upload bool wasFileUploaded = AttemptUploadSingleFile(thisFilePath, projectIdForUploads, dbCredentialsIfAny, publishSettings); if (wasFileUploaded) { countSuccess++; } } catch (Exception ex) { countFailure++; StatusLog.AddError("Error uploading datasource " + thisFilePath + ". " + ex.Message); LogManualAction_UploadDataSource(thisFilePath); } } } //If we are running recursive , then look in the subdirectories too if (recurseDirectories) { int subDirSuccess; int subDirFailure; foreach (var subDirectory in Directory.GetDirectories(currentContentPath)) { UploadDirectoryToServer(rootContentPath, subDirectory, projectsList, true, out subDirSuccess, out subDirFailure); countSuccess += subDirSuccess; countFailure += subDirFailure; } } }
/// <summary> /// Looks up the default project ID /// </summary> /// <returns></returns> public string GetProjectIdForUploads(string projectName) { //If the project name is empty - look for the default project if (string.IsNullOrEmpty(projectName)) { goto find_default_project; } //Look for the matching project var project = _projectsList.FindProjectWithName(projectName); if (project != null) { return(project.Id); } //If the option is specified; attempt to create the project if (_uploadProjectBehavior.AttemptProjectCreate) { //Create the project name var createProject = new SendCreateProject(_onlineUrls, _onlineSession, projectName); try { var newProject = createProject.ExecuteRequest(); _projectsList.AddProject(newProject); return(newProject.Id); } catch (Exception exCreateProject) { this.StatusLog.AddError("Failed attempting to create project '" + projectName + "', " + exCreateProject.Message); } } //If we are not allowed to fall back the default project then error if (!_uploadProjectBehavior.UseDefaultProjectIfNeeded) { throw new Exception("Not allowed to use default project"); } this.StatusLog.AddStatus("Project name not found '" + projectName + "'. Reverting to default project", -10); find_default_project: //If all else fails, fall back to using the default project var defaultProject = _projectsList.FindProjectWithName("default"); //Find the default project if (defaultProject != null) { return(defaultProject.Id); } defaultProject = _projectsList.FindProjectWithName("Default"); if (defaultProject != null) { return(defaultProject.Id); } defaultProject = _projectsList.FindProjectWithName(""); //Try empty if (defaultProject != null) { return(defaultProject.Id); } //Default project not found. Choosing any project this.StatusLog.AddError("Default project not found. Reverting to any project"); foreach (var thisProj in _projectsList.Projects) { return(thisProj.Id); } StatusLog.AddError("Upload could not find a project ID to use"); throw new Exception("Aborting. Upload Datasources could not find a project ID to use"); }
/// <summary> /// Update the group /// </summary> /// <returns>TRUE: Success. FALSE: failed/error</returns> private bool UpdateGroup() { AppDiagnostics.Assert(!string.IsNullOrWhiteSpace(this.GroupId), "920-1053: missing group id"); AppDiagnostics.Assert(!string.IsNullOrWhiteSpace(this.UpdatedGroupName), "920-1054: missing role"); //[2020-09-20] NOTE: Currently this function DOES NOT work for Active Directory syncronized groups (on premises Tableau Serer) //ref: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref.htm#update_group var sb = new StringBuilder(); var xmlWriter = XmlWriter.Create(sb, XmlHelper.XmlSettingsForWebRequests); xmlWriter.WriteStartElement("tsRequest"); xmlWriter.WriteStartElement("group"); xmlWriter.WriteAttributeString("name", this.UpdatedGroupName); //If we are updating the grant license parts, write these here //[2020-09-20] For (on premises Server) Active Directory sync, this would need to be different XML (inside an "import" node) if (this.PerformUpdateGrantLicense) { xmlWriter.WriteAttributeString("grantLicenseMode", this.UpdatedGrantLicenseMode); //If the Grant license mode is blank, set the license role to be UNLICENSED //This is required to remove the licensing mode: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref.htm#update_group string updateLicenseMode = this.UpdatedGrantLicenseSiteRole; if (string.IsNullOrEmpty(this.UpdatedGrantLicenseMode)) { updateLicenseMode = "UNLICENSED"; } xmlWriter.WriteAttributeString("minimumSiteRole", updateLicenseMode); } xmlWriter.WriteEndElement(); //</group> xmlWriter.WriteEndElement(); // </tsRequest> xmlWriter.Close(); var xmlText = sb.ToString(); //Get the XML text out //Create a web request var urlUpdateGroup = this._onlineSession.ServerUrls.Url_UpdateSiteGroup(_onlineSession, this.GroupId); var webRequest = this.CreateLoggedInWebRequest(urlUpdateGroup, "PUT"); TableauServerRequestBase.SendPutContents(webRequest, xmlText); //Get the response var response = GetWebReponseLogErrors(webRequest, "update group (change name/grant-license)"); using (response) { var xmlDoc = GetWebResponseAsXml(response); //Get all the group nodes var nsManager = XmlHelper.CreateTableauXmlNamespaceManager("iwsOnline"); var xNodeUpdatedGroup = xmlDoc.SelectSingleNode("//iwsOnline:group", nsManager); try { //Sanity check on the expected results var updatesGroupId = xNodeUpdatedGroup.Attributes["id"].Value; var returnedMinSiteRole = XmlHelper.SafeParseXmlAttribute(xNodeUpdatedGroup, "minimumSiteRole", ""); if (updatesGroupId != this.GroupId) { IwsDiagnostics.Assert(false, "920-1102: Error. Updated groups returned mismatching group id: " + this.GroupId + "/" + updatesGroupId); StatusLog.AddError("920-1102: Error. Updated groups returned mismatching group id: " + this.GroupId + "/" + updatesGroupId); return(false); } //See if the returned role matches if (this.PerformUpdateGrantLicense) { if (!CompareGrantLicenseRoles(returnedMinSiteRole, this.UpdatedGrantLicenseSiteRole)) { string errorText = "920-1206: Error. Updated Grant License role for group does not match expected role: " + NullSafeText(returnedMinSiteRole) + "/" + NullSafeText(this.UpdatedGrantLicenseSiteRole); StatusLog.AddError(errorText); return(false); } } } catch (Exception parseXml) { StatusLog.AddError("920-1105: Update group, error parsing XML response " + parseXml.Message + "\r\n" + xmlDoc.InnerXml); return(false); } } return(true); //Success }
private SiteUser UpdateUser(string userId, string newRole, bool updateAuthentication, SiteUserAuth newAuthentication) { AppDiagnostics.Assert(!string.IsNullOrWhiteSpace(userId), "missing user id"); AppDiagnostics.Assert(!string.IsNullOrWhiteSpace(newRole), "missing role"); //ref: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref.htm#update_user var sb = new StringBuilder(); var xmlWriter = XmlWriter.Create(sb, XmlHelper.XmlSettingsForWebRequests); xmlWriter.WriteStartElement("tsRequest"); xmlWriter.WriteStartElement("user"); xmlWriter.WriteAttributeString("siteRole", newRole); //Only if we are updating the user's authentication method do we need to update this if (updateAuthentication) { string newAuthenticationText = SendCreateUser.SiteUserAuthToAttributeText(newAuthentication); xmlWriter.WriteAttributeString("authSetting", newAuthenticationText); } xmlWriter.WriteEndElement(); //</user> xmlWriter.WriteEndElement(); // </tsRequest> xmlWriter.Close(); var xmlText = sb.ToString(); //Get the XML text out //Create a web request var urlUpdateUser = _onlineUrls.Url_UpdateSiteUser(_onlineSession, userId); var webRequest = this.CreateLoggedInWebRequest(urlUpdateUser, "PUT"); TableauServerRequestBase.SendPutContents(webRequest, xmlText); //Get the response var response = GetWebReponseLogErrors(webRequest, "update user (change auth or role)"); using (response) { var xmlDoc = GetWebResponseAsXml(response); //Get all the user nodes var nsManager = XmlHelper.CreateTableauXmlNamespaceManager("iwsOnline"); var xNodeUser = xmlDoc.SelectSingleNode("//iwsOnline:user", nsManager); try { if (updateAuthentication) { return(SiteUser.FromUserXMLWithoutUserId(xNodeUser, userId)); } else { //Use the passed in authentication return(SiteUser.FromUserXMLWithoutUserIdOrAuthRole(xNodeUser, userId, newAuthentication)); } } catch (Exception parseXml) { StatusLog.AddError("Update user, error parsing XML response " + parseXml.Message + "\r\n" + xNodeUser.InnerXml); return(null); } } }