In Smartsheet I have downloaded Published Items list from user management. Using this list I need to change the Access control (Public/Organization) value through api. Please help. Thanks
Depending on the type of object you're updating (Sheet, Report, or Dashboard), the operations you'll use are as follows:
Set Sheet Publish Status
Set Report Publish Status
Set Dashboad (Sight) Publish Status
I've included some code examples below (taken nearly verbatim from those you'll find in the docs I've linked to above.)
One additional note: You'll notice that the values written to the Published Items list that you manually export from Smartsheet differ from the values used by the API. For example, the Access Control column of exported data contains values like Organization and Public -- where as the corresponding values used by the API are ORG and ALL.
Example #1: SHEET --> Set ReadOnlyFullAccessibleBy to ALL, ReadWriteAccessibleBy to ORG
SheetPublish sheetPublish = new SheetPublish();
sheetPublish.ReadOnlyLiteEnabled = false;
sheetPublish.ReadOnlyFullEnabled = true;
sheetPublish.ReadOnlyFullAccessibleBy = "ALL";
sheetPublish.ReadWriteEnabled = true;
sheetPublish.ReadWriteAccessibleBy = "ORG";
sheetPublish.IcalEnabled = false;
smartsheet.SheetResources.UpdatePublishStatus(
4583614634583940, // sheetId
sheetPublish
);
Example #2: Report --> Set ReadOnlyFullAccessibleBy to ORG
ReportPublish reportPublish = new ReportPublish();
reportPublish.ReadOnlyFullEnabled = true;
reportPublish.ReadOnlyFullAccessibleBy = "ORG";
smartsheet.ReportResources.UpdatePublishStatus(
1653087851556740, // reportId
reportPublish
);
Example #3: Dashboard (Sight) --> Set ReadOnlyFullAccessibleBy to ALL
SightPublish publish = new SightPublish();
publish.ReadOnlyFullEnabled = true;
publish.ReadOnlyFullAccessibleBy = "ALL";
smartsheet.SightResources.SetPublishStatus(
5363568917931908, // sightId
publish
);
Related
How do I tell if a sheet belongs to a certain group?
For example, we have a group called RPR and when we create sheets we share them with other users in our organization applying the group to the sheet.
// Get all sheets modified within last day
SmartSheetList = smartsheet
.SheetResources
.ListSheets(new SheetInclusion[] { SheetInclusion.OWNER_INFO }, new PaginationParameters(true, null, null))
.Data
.Where(d => d.ModifiedAt.Value.Date >= DateTime.Now.AddDays(-1).Date)
.ToList();
// Get organization sheets
var orgUserSheets = smartsheet.UserResources.SheetResources.ListSheets(true).Data.ToList();
// get specific group
var group = smartsheet.GroupResources.ListGroups(null).Data.Where(grp => grp.Id == some-id-here).First();
With the code above I can see if a sheet belongs to an organization but I can't tell if that sheet belongs to a certain group. Any help would be appreciated.
You need one more API Request for each sheet to retrieve the list of shares on the sheet. Keep in mind, sheets can be shared directly on the sheet or via a workspace.
To get sheets that are shared with a specific group directly or via a workspace, you can use code something like the below (relying on LINQ for this sample).
SmartsheetClient cl = new SmartsheetBuilder()
.SetAccessToken(ACCESS_TOKEN)
.Build();
var includeAll = new PaginationParameters(true, null, null);
Console.WriteLine("Looking for group " + SOUGHT_GROUP_NAME);
var groups = cl.GroupResources.ListGroups(includeAll);
var soughtGroup = groups.Data.Single((group) => group.Name == SOUGHT_GROUP_NAME);
Console.WriteLine("Found group ID {0} for group {1}.", soughtGroup != null ? soughtGroup.Id.ToString() : "NULL", SOUGHT_GROUP_NAME);
if (soughtGroup == null)
throw new ArgumentException("Group not found");
var sheets = cl.SheetResources.ListSheets(null, includeAll);
Console.WriteLine("Querying through {0} sheets...", sheets.Data.Count);
var sheetsSharedWithGroup = from sheet in sheets.Data
from share in cl.SheetResources.ShareResources.ListShares(sheet.Id.Value, includeAll, ShareScope.Workspace).Data
where share.GroupId == soughtGroup.Id
select new { Sheet = sheet, Share = share };
var found = sheetsSharedWithGroup.ToList();
Console.WriteLine("Found {0} sheets shared with group {1}.", found.Count, SOUGHT_GROUP_NAME);
found.ForEach(foundSheet => Console.WriteLine("Sheet {0} shared with {1} ({2})", foundSheet.Sheet.Name, foundSheet.Share.Name, foundSheet.Share.Type));
NOTE: I submitted a pull request to add support for the specific ListShares overload that returns workspace shares too. This is available in the REST API but wasn't yet in the C# SDK.
NOTE: The above code does not take into account Smartsheet Sights yet. It should be possible using the corresponding REST API for Sights (i.e. List Sights, List Sight Shares, but I don't think that's in the C# SDK yet.
I managed to create sections at very specific places within my OneNote Notebooks. Now I want to achieve the same with pages. So instead of using the unpredicteable-placing "CreateNewPage" method, I use UpdateHierarchy which works perfectly fine (for testing purpose I'm using AppendChild below).
The only issue I'm having is, that after creating the new page using UpdateHierarchy I'm completely loosing any links to the newly created page. OneNote assigns an ID and ignores all further Tags/Names I give. Also setting the One:T member used for setting the title is getting ignored - it always creates an "Untitled Page".
Am I doing anything wrong or do I need to first CreateNewPage and, using the assigned page-ID, re-place it using UpdateHierarchy?
Regards
Joel
function createPage {
param([string]$title, [string]$sectionnode)
[string]$pageref=$null
# Gather the pages within the notebook
[xml]$ref = $null
$_globalOneNote["COM"].GetHierarchy($sectionnode, [Microsoft.Office.InterOp.OneNote.HierarchyScope]::hsPages, [ref]$ref)
[System.Xml.XmlNamespaceManager] $nsmgr = $ref.NameTable
$nsmgr.AddNamespace('one', "http://schemas.microsoft.com/office/onenote/2010/onenote")
# Creation of a new page
$newPage = $ref.CreateElement('one', 'Page', 'http://schemas.microsoft.com/office/onenote/2010/onenote')
$newTitle = $ref.CreateElement('one', 'Title', 'http://schemas.microsoft.com/office/onenote/2010/onenote')
$newOE = $ref.CreateElement('one', 'OE', 'http://schemas.microsoft.com/office/onenote/2010/onenote')
$newT = $ref.CreateElement('one', 'T', 'http://schemas.microsoft.com/office/onenote/2010/onenote')
$newPage.SetAttribute('name', "Olololo")
$newT.InnerText = '<![CDATA[Testtitle]]>'
$newOE.AppendChild($newT)
$newTitle.AppendChild($newOE)
$newPage.AppendChild($newTitle)
$ref.Section.AppendChild($newPage)
$_globalBJBOneNote["COM"].UpdateHierarchy($ref.OuterXML)
}
This does the trick. Setting of title etc. must still be done using UpdatePageContent however the new page is placed properly. For putting metadata (title, indention etc.) a separate function may be used that works using the returned GUID of the createPage function.
function createPage {
param([string]$sectionnode, [string]$pagenode = $sectionnode)
# Gather the sections within the notebook
[xml]$ref = $null
$_globalBJBOneNote["COM"].GetHierarchy($sectionnode, [Microsoft.Office.InterOp.OneNote.HierarchyScope]::hsPages, [ref]$ref)
[System.Xml.XmlNamespaceManager] $nsmgr = $ref.NameTable
$nsmgr.AddNamespace('one', "http://schemas.microsoft.com/office/onenote/2010/onenote")
# Create new page
[string]$pageID = $null
$_globalBJBOneNote["COM"].createNewPage($ref.Section.ID, [ref]$pageID)
# Reload the hierarchy, now we can get the node of the new notebook
$_globalBJBOneNote["COM"].GetHierarchy($sectionnode, [Microsoft.Office.InterOp.OneNote.HierarchyScope]::hsPages, [ref]$ref)
$newPageNode = $ref.SelectSingleNode('//one:Page[#ID="' + $pageID + '"]', $nsmgr)
$referencePageNode = $ref.SelectSingleNode('//one:Page[#ID="' + $pagenode + '"]', $nsmgr)
# Reposition
[void]$ref.Section.removeChild($newPageNode)
[void]$ref.Section.InsertAfter($newPageNode, $referencePageNode)
$_globalBJBOneNote["COM"].UpdateHierarchy($ref.OuterXML)
$pageID
}
I have a little requirement that is making me crazy:
We have 8 different forms for the Contact Entity.
We also have a pick list with 8 options.
The idea is that based on the option selected we could open that Contact record showing by default a particular form WITHOUT USING JAVASCRIPT in order to avoid performance problems (each record has to be loaded twice). Example:
Forms:
Form 1
Form 2
Form 3
Pick List Values - Default Form:
Form 1
Form 2
Form 3
If Form 3(pick list value) is selected then, the next time I open that record, Form 3 should be displayed by default.
If Form 1(pick list value) is selected then, the next time I open that record, Form 1 should be displayed by default.
I've trayed registering a plugin at the systemform entity, in RetrieveFilteredForms message, updating the userentityuisettings table and I've been able to set a "DEFAULT" that is displayed every time the records is opened regardless the last opened form.
I've trayed registering a plugin at the contact entity, in Retrieve message, updating the userentityuisettings table but I found that Crm only consults the table Once if there is no attribute updated, the following times Crm take the the default form to open value from the cache.
This is an old question but since it's coming up in my searches for this problem I wanted to add my solution.
We use Dynamics CRM 2013. To my knowledge, later versions of 2011 also support this technique.
The form that is displayed when an entity is opened is determined by a few things - the default form, the security roles and fallback settings for a form and the last form used by the current user for that entity. We had a similar problem to the asker where we wanted a different account form displayed based on the value of a form. We were also tired of the constant reload/refresh that javascript techniques are subject to.
I found some blog posts (in particular this one: http://gonzaloruizcrm.blogspot.com/2014/11/avoiding-form-reload-when-switching-crm.html) which mentioned that it is possible to write a plugin to the Retrieve of the entity that allows you to read out the value (LastViewedFormXml) from UserEntityUISettings that stores which form was last used. If it's not the form you want, you can write in the desired value. This avoids the javascript form refreshing.
I had to modify some code from the samples I found to get it to work, but I'm happy with the results. You need to generate an entity class using CrmSvcUtil and include it in the project. You can get your form guids from the url of the form editor.
using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Description;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Sdk.Client;
namespace CRM.Plugin.AccountFormSwitcher
{
public class Plugin : IPlugin
{
public enum accountType
{
Customer = 100000000,
Vendor = 100000001,
Partner = 100000002,
Other = 100000003
}
public const string CustomerAccountFormId = "00000000-E53C-4DF4-BC99-93856EDD168C";
public const string VendorAccountFormId = "00000000-E49E-4197-AB5E-F353EF0E806E";
public const string PartnerAccountFormId = "00000000-B8C6-4E2B-B84E-729AA11ABE61";
public const string GenericAccountFormId = "00000000-8F42-454E-8E2A-F8196B0419AF";
public const string AccountTypeAttributeName = "cf_accounttype";
public void Execute(IServiceProvider serviceProvider)
{
if (serviceProvider == null)
{
throw new ArgumentNullException("serviceProvider");
}
// Obtain the execution context from the service provider.
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
var pluginContext = (IPluginExecutionContext)context;
if (pluginContext.Stage == 20) //pre-operation stage
{
var columns = (ColumnSet)pluginContext.InputParameters["ColumnSet"];
if (!columns.Columns.Contains(AccountTypeAttributeName))
columns.AddColumn(AccountTypeAttributeName);
}
else if (pluginContext.Stage == 40) //post-operation stage
{
EntityReference currentEntity = (EntityReference)context.InputParameters["Target"];
if (currentEntity == null)
return;
var query = new QueryExpression(Account.EntityLogicalName);
query.Criteria.AddCondition("accountid", ConditionOperator.Equal, currentEntity.Id);
query.ColumnSet = new ColumnSet(AccountTypeAttributeName);
var accounts = service.RetrieveMultiple(query).Entities;
Account currentAccount = (Account)accounts[0];
SetForm(currentAccount, service, context.UserId);
}
}
private void SetForm(Account account, IOrganizationService service, Guid userId)
{
var query = new QueryExpression(UserEntityUISettings.EntityLogicalName);
query.Criteria.AddCondition("ownerid", ConditionOperator.Equal, userId);
query.Criteria.AddCondition("objecttypecode", ConditionOperator.Equal, Account.EntityTypeCode);
query.ColumnSet = new ColumnSet("lastviewedformxml");
var settings = service.RetrieveMultiple(query).Entities;
// Some users such as SYSTEM have no UserEntityUISettings, so skip.
if (settings == null || settings.Count != 1 || account.cf_AccountType == null) return;
var setting = settings[0].ToEntity<UserEntityUISettings>();
string formToUse;
switch ((accountType)account.cf_AccountType.Value)
{
case accountType.Customer:
formToUse = String.Format("<MRUForm><Form Type=\"Main\" Id=\"{0}\" /></MRUForm>", CustomerAccountFormId);
break;
case accountType.Vendor:
formToUse = String.Format("<MRUForm><Form Type=\"Main\" Id=\"{0}\" /></MRUForm>", VendorAccountFormId);
break;
case accountType.Partner:
formToUse = String.Format("<MRUForm><Form Type=\"Main\" Id=\"{0}\" /></MRUForm>", PartnerAccountFormId);
break;
case accountType.Other:
formToUse = String.Format("<MRUForm><Form Type=\"Main\" Id=\"{0}\" /></MRUForm>", GenericAccountFormId);
break;
default:
formToUse = String.Format("<MRUForm><Form Type=\"Main\" Id=\"{0}\" /></MRUForm>", GenericAccountFormId);
return;
}
// Only update if the last viewed form is not the one required for the given opportunity type
if (!formToUse.Equals(setting.LastViewedFormXml, StringComparison.InvariantCultureIgnoreCase))
{
var s = new UserEntityUISettings { Id = setting.Id, LastViewedFormXml = formToUse };
service.Update(s);
}
}
}
}
And to address the asker's issue with it only consulting the UserEntityUISettings once, I'm not sure why that's happening. But why not use javascript to change the form when the triggering attribute is changed? That what I do and I haven't ran into any problems with the plugin not displaying the desired for.
EDIT: the OP specified after that he needs a solution without javascript, I leave this reply for future references.
You can use javascript, inside the OnLoad event you check the picklist value and navigate to the desired form. Check this code as example
var value = Xrm.Page.getAttribute("new_optionset").getValue();
switch(value) {
case 100000000:
Xrm.Page.ui.formSelector.items.get(0).navigate();
break;
case 100000001:
Xrm.Page.ui.formSelector.items.get(1).navigate();
break;
case 100000002:
Xrm.Page.ui.formSelector.items.get(2).navigate();
break;
/// ... other cases here
default:
// default form to open when there is no value
Xrm.Page.ui.formSelector.items.get(0).navigate();
}
This is an oldy but a goody... I use CRM Rules to generate Hide Tab and Show Tab actions that essentially show each user role a different form.
Steps:
Create one, massive form with all of the fields you want to display (if you already have many forms, this would include all fields across all forms).
Organize the form into TABS, with each tab showing 'one form' worth of data. (You can also have many TABS for each user group). Typically, I create one 'General' tab that has the key option set that will set the rest of the form up, and any fields that are common across roles / user groups / forms, like status, name, etc...
3) Hide all of the tabs except the General tab by unchecking the visible box on those tab form property forms in the Admin UI.
4) Using CRM Rules (crm-rules.com), you can then bring in the metadata, with the form, and all of the tabs and sections in there. Then you just have to write one rule for each 'form' you are trying to show... Each rule is of this format:
IF User_Role contains 'Sales' THEN Show Tab: Sales
IF User_Role contains 'Marketing' THEN Show Tab: Marketing
You can also do this, of course, with an option set, or any field on the form as the condition... One of the benefits of this approach is that if users cross role boundaries (or some users were part of security roles that could access multiple forms), this technique shows them both forms at once...
HTH somebody, CRM Rules (www.crm-rules.com) generates the JavaScript to make this happen...
I am using the Fluent API to handle various configuration options for Logging using EntLib.
I am building up the loggingConfiguration section manually in code. It seems to work great except that the RollingFlatFileTraceListener doesn't actually Roll the file. It will respect the size limit and cap the amount of data it writes to the file appropriately, but it doesn't not actually create a new file and continue the logs.
I've tested it with a sample app and the app.config and it seems to work. So I'm guess that I am missing something although every config option that seems like it needs is there.
Here is the basics of the code (with hard-coded values to show a config that doesn't seem to be working):
//Create the config builder for the Fluent API
var configBuilder = new ConfigurationSourceBuilder();
//Start building the logging config section
var logginConfigurationSection = new LoggingSettings("loggingConfiguration", true, "General");
logginConfigurationSection.RevertImpersonation = false;
var _rollingFileListener = new RollingFlatFileTraceListenerData("Rolling Flat File Trace Listener", "C:\\tracelog.log", "----------------------", "",
10, "MM/dd/yyyy", RollFileExistsBehavior.Increment,
RollInterval.Day, TraceOptions.None,
"Text Formatter", SourceLevels.All);
_rollingFileListener.MaxArchivedFiles = 2;
//Add trace listener to current config
logginConfigurationSection.TraceListeners.Add(_rollingFileListener);
//Configure the category source section of config for flat file
var _rollingFileCategorySource = new TraceSourceData("General", SourceLevels.All);
//Must be named exactly the same as the flat file trace listener above.
_rollingFileCategorySource.TraceListeners.Add(new TraceListenerReferenceData("Rolling Flat File Trace Listener"));
//Add category source information to current config
logginConfigurationSection.TraceSources.Add(_rollingFileCategorySource);
//Add the loggingConfiguration section to the config.
configBuilder.AddSection("loggingConfiguration", logginConfigurationSection);
//Required code to update the EntLib Configuration with settings set above.
var configSource = new DictionaryConfigurationSource();
configBuilder.UpdateConfigurationWithReplace(configSource);
//Set the Enterprise Library Container for the inner workings of EntLib to use when logging
EnterpriseLibraryContainer.Current = EnterpriseLibraryContainer.CreateDefaultContainer(configSource);
Any help would be appreciated!
Your timestamp pattern is wrong. It should be yyy-mm-dd instead of MM/dd/yyyy. The ‘/’ character is not supported.
Also, you could accomplish your objective by using the fluent configuration interface much easier. Here's how:
ConfigurationSourceBuilder formatBuilder = new ConfigurationSourceBuilder();
ConfigurationSourceBuilder builder = new ConfigurationSourceBuilder();
builder.ConfigureLogging().LogToCategoryNamed("General").
SendTo.
RollingFile("Rolling Flat File Trace Listener")
.CleanUpArchivedFilesWhenMoreThan(2).WhenRollFileExists(RollFileExistsBehavior.Increment)
.WithTraceOptions(TraceOptions.None)
.RollEvery(RollInterval.Minute)
.RollAfterSize(10)
.UseTimeStampPattern("yyyy-MM-dd")
.ToFile("C:\\logs\\Trace.log")
.FormatWith(new FormatterBuilder().TextFormatterNamed("textFormatter"));
var configSource = new DictionaryConfigurationSource();
builder.UpdateConfigurationWithReplace(configSource);
EnterpriseLibraryContainer.Current = EnterpriseLibraryContainer.CreateDefaultContainer(configSource);
var writer = EnterpriseLibraryContainer.Current.GetInstance<LogWriter>();
DateTime stopWritingTime = DateTime.Now.AddMinutes(10);
while (DateTime.Now < stopWritingTime)
{
writer.Write("test", "General");
}
I am using sessions to populate a multi select box with options in my Zend application.
The user selects one or more options and fills in other fields on the form and then submits. If the user didn't select all of the options in the multi select then the form is displayed again but the multi select only has the options that the user did not select the last time. This process goes on until there are no more options from the multi select left to process.
Here is the code I use to get rid of the options that have already been processed so that they are not used to populate the multi select box:
if($form_successful){
// TODO remove $post['keyword_names'] (i.e. already processed) from $keyword_names (that come from $_SESSION)
$keyword_names = array_diff($keyword_names, $post['keyword_names']);
print_r($keyword_names);
if(is_array($keyword_names) && !empty($keyword_names)){
// save updated $keyword_names into $_SESSION['workflow1']
$session = new Zend_Session_Namespace('workflow1');
$session->keyword_names = $keyword_names;
// set flag to false so that we display form again
$form_successful = false;
}else{ // all keywords have been assigned
// go to next step
$this->_redirect('/workflow-1/step-'.($step+1).'/');
}
}
print_r($keyword_names); displays the correct options, however when the form is loaded when the user submits, the multi select displays the options that were there from the begining ie the options the user has just selected and submitted are not being taken out of the multi select, it is only when the user submits the form again then the multi select box updates.
Appreciate the help.
Solved the issue by making use of URL parameters. Here is the code (might differ a lot from what I posted first because some big changes were made):
// after successful form submission
if($form_successful){
// remove $post['keyword_names'] (i.e. already processed) from $keyword_names (that come from $_SESSION)
$keyword_names = array_diff($keyword_names, $post['keyword_names']);
// save remaining $keyword_names into $_SESSION['workflow1']
$session = new Zend_Session_Namespace('workflow1');
$session->keyword_names = $keyword_names;
if(is_array($keyword_names) && !empty($keyword_names)){
// redirect to the same step again - to ensure that the form will reflect (in select lists) newly created AdGroup and/or Campaign
// GET parameteres ($params_array) provide a way to remember user's choice
$params_array = array();
if(!empty($post['match_type_id'])){
$params_array['match_type_id'] = $post['match_type_id'];
}
if(!empty($post['with_permutations'])){
$params_array['with_permutations'] = $post['with_permutations'];
}
if(!empty($ad_group_id)){
$params_array['ad_group_id'] = $ad_group_id;
}
$this_step_url = UrlUtils::assemble('', $this->getRequest()->getActionName(), $this->getRequest()->getControllerName(), $this->getRequest()->getModuleName(), $params_array);
$this->_redirect($this_step_url);
}else{ // all keywords have been assigned
// go to next step
$this->_redirect('/workflow-1/step-'.($step+1).'/');
}
}
So you don't have any code about Zend_Form object here. How do you populate the form element? If you post your class code which extends Zend_Form (or any other code dials with your form) then I may help. But in any case you can populate your multiselectbox with setMultiOptions() method or addMultiOption() for each item in multiselectbox.