How can you create a Project Sheet in SmartSheets with the API - smartsheet-api

I'm a new user of the SmartSheets API, attempting to create an integration. I have projects that are created in an external system that I wish to represent in a Project Sheet in SmartSheets, but in my attempts so far using the SmartSheets API, all I can create is a simple grid. Is there an API that will allow me to create a Project Sheet in SmartSheets?

A project sheet seems to be a regular sheet with a Gant View activated Smarsheet Sheet object documentation
When you create the sheet object, make sure you activate the boolean option "ganttEnabled" or "gant_enabled" to True.
# from documentation Sample 1: Create sheet in folder
sheet_spec = smartsheet.models.Sheet({
'name': 'newsheet',
'columns': [{
'title': 'Favorite',
'type': 'CHECKBOX',
'symbol': 'STAR'
}, {
'title': 'Primary Column',
'primary': True,
'gant_enabled': True,
'type': 'TEXT_NUMBER'
}
]
})
response = smartsheet_client.Folders.create_sheet_in_folder(3734419270854532,
sheet_spec)
new_sheet = response.result
After testing this option I found that the object has the properties, but the API fails to create the object, so an alternative solution was,
Manually create a project
Manually create a template from the project (Save as Template)
use the API to create a sheet using the template
import smartsheet
smart = smartsheet.Smartsheet()
smart.errors_as_exceptions(True)
fldr = <folder ID>
temp = <Template ID>
sht_spec = smart.models.Sheet({'name': 'sht_name', 'from_id': temp})
response = smart.Folders.create_sheet_in_folder(fldr,sht_spec)
new_sht = response.result

You can autonomate the full process:
have a project sheet sitting somewhere for a system account you own.
share the sheet to the desired account (as viewer)
copy the sheet with that second (the second account is now the woner of the sheet)
unshare the first sheet (no longer needed)

Related

Adding new data with identical fields to Firebase without overwriting old data? Flutter

So, essentially I'm trying to allow my users to click a button and add each individual username to a document on Firebase. However, .set() obviously sets brand new information while .update() overwrites the old information. I've tried using SetOptions(merge: true) when writing to Firebase, but that hasn't proven effective either.
I would love it if someone could point me in the right direction and/or explain what I'm doing wrong. This is how I'm handling it currently:
CollectionReference scatterSignup =
FirebaseFirestore.instance.collection('scatterSignup');
onPressed: () async {
scatterUp.name = username;
scatterUp.code = userCode;
final scatter = scatterSignup.doc(regionUpload).set({
'name': scatterUp.name,
'code': scatterUp.code,
}, SetOptions(merge: true));
}),
I expected new information to be added without overwriting (which I believe is the function of merge unless I'm mistaken?) but that definitely didn't happen.
Essentially, I'm trying to figure out how to have a single document that consists of multiple name and code fields: one for each user trying to sign up for the list.

Need to show a card widget and after some delay automatically show another card widget regarding google workspace add-on creation

I need to show homeCard() and after I need to show settingsCard() automatically. Since I coudn't find a right method in app-script documentation I need some help for do this task.
Here I provided the code
function nevigateToUserSelectionPage(e) {
var navigation = CardService.newNavigation();
var builder = CardService.newActionResponseBuilder();
var userSelectionCardNavigation = navigation.pushCard(settingsCard());
return builder.setNavigation(userSelectionCardNavigation).build();
}
function homeCard() {
builder = CardService.newCardBuilder();
section = CardService.newCardSection();
let participantsText = CardService.newTextParagraph()
.setText("<u>Home card here</u>");
let blink = CardService
.newImage()
.setImageUrl('https://res.cloudinary.com/deez2bddk/image/upload/v1646709349/icons8-dots-loading_x9q7jv.gif');
section.addWidget(blink);
section.addWidget(participantsText);
section.addWidget(AddSplah);
builder.addSection(section);
console.log('home card triggered!!!');
return builder.build();
}
function settingsCard() {
//const myTimeout = setTimeout(5000);
Utilities.sleep(10000);
builder = CardService.newCardBuilder();
section = CardService.newCardSection();
console.log('Settings card triggered!!!');
let participantsText = CardService.newTextParagraph()
.setText("<u>This is Settings Page....</u>");
section.addWidget(participantsText);
section.addWidget(getAuthenticationStepperImage());
builder.addSection(section);
return builder.build();
}
in code.gs file
function mainController() {
return homeCard();
}
Above code blocks I need to execute homeCard() function and then , settingsCard() but I can`t find a proper solution in workspace add-on creation documentation provided by google.
After doing some research, I think the CardService does not provide a method for non-interactive updates.
You can update the view based on user click interaction, as you can see in the Cats Quickstart. When the user clicks the cat image changes the image updates due the URL has a new parameter via new Date().getTime().
Apart from this, you have the triggers provided by Google, such as: homepageTrigger for common use case or onItemsSelectedTrigger specifically for Drive. You can review the full list here.
In summary: I think that what are you trying to achieve actually is not currently feasible within CardService.
If you wish Google adds some kind of time driven trigger to Google Workspace Add-ons, request it via this form.
Remember that in the actual state, HTML/CSS is not allowed, maybe this would be another possible path for your Feature Request.

How can I create and update pages dynamically in Sulu CMS?

I have the following situation:
A database stores information about houses (address, number of rooms, date built, last selling price, etc.)
This database is being manipulated through an app (let's call that app the "backend house app") that cannot be directly integrated in a Sulu-driven app. I can access the stored data through an API that gives me JSON-representations of House-objects. I can also have the app launch some sort of call to a Sulu-driven app when a house is created, updated or deleted.
The Sulu-driven app (let's call that the "frontend house app") with templates for "house", "room", etc., is connected to a different database on a different server. This Sulu-driven app's website-environment shows house-pages with room-pages where some content is pre-filled through a connection to the "backend house app". Other content only exists on the database of the "frontend house app", like user comments, appraisals of interior design, etc., according to configured aspects of the Sulu-templates.
What I want to achieve, is a way to automate the creation, updating and deletion of "frontend house app"-pages based on activity in the "backend house app".
For instance, when a new house is added in the "backend house app", I want it to notify the "frontend house app" so that the "frontend house app" will automatically create the entire node-tree for the newly added house. Meaning: a "house"-page with the required data filled in, "room"-pages for each room, etc., so that the content manager of the "frontend house app" can see the entire tree of the newly added house in the workspace and can start manipulating content in the already available templates. In addition to automatically creating these pages, I also want to pre-set the rights to update and create, since the content manager of the "frontend house app" must not be able to create new rooms or change the name of the house, for instance.
I did not manage to get it working, I'll just add what I already done to show where I got stuck.
I started out with the following code, in a controller that extends Sulu's own WebsiteController:
$documentManager = $this->get('sulu_document_manager.document_manager');
$nodeManager = $this->get('sulu_document_manager.node_manager');
$parentHousesDocument = $documentManager->find('/cmf/immo/routes/nl/huizen', 'nl');
$newHouseDocument = $documentManager->create('page');
// The backendApi just gives a House object with data from the backend
// In this case we get an existing House with id 1
$house = $backendApi->getHouseWithId(1);
$newHouseDocument->setTitle($house->getName()); // For instance 'Smurfhouse'
$newHouseDocument->setLocale('nl'); // Nl is the only locale we have
$newHouseDocument->setParent($parentHouseDocument); // A default page where all the houses are listed
$newHouseDocument->setStructureType('house'); // Since we have a house.xml template
// I need to grab the structure to fill it with values from the House object
$structure = $newHouseDocument->getStructure();
$structure->bind([
'title' => $house->getName(),
'houseId' => $house->getId(),
]);
$newHouseDocument->setWorkflowStage(WorkflowStage::PUBLISHED); // You would expect this to automatically publish the document, but apparently it doesn't... I took it from a test I reverse-engineered in trying to create a page, I have no clue what it is supposed to change.
$nodeManager->createPath('/cmf/immo/routes/nl/huizen/' . $house->getId());
$documentManager->persist(
$newHouseDocument,
'nl',
[
'path' => '/cmf/immo/contents/huizen/' . Slugifier::slugify($house->getName()), // Assume for argument's sake that the Slugifier just slugifies the name...
'auto_create' => true, // Took this value from a test that creates pages, don't know whether it is necessary
'load_ghost_content' => false, // Idem
]
);
$documentManager->flush();
Now, when I fire the controller action, I first get the exception
Property "url" in structure "house" is required but no value was given.
I tried to fix this by just manually binding the property 'url' with value '/huizen/' . $house->getId() to $structure, at the point where I bind the other values. But this doesn't fix it, as apparently the url value is overwritten somewhere in the persist event chain, and I haven't yet found where.
However, I can, just for testing purposes, manually override the url in the StructureSubscriber that handles the mapping for this particular persist event. If I do this, something gets created in the Sulu-app-database - hurray!
My phpcr_nodes table lists two extra records, one for the RouteDocument referring to /cmf/immo/routes/nl/huizen/1, and one for the PageDocument referring to /cmf/immo/contents/huizen/smurfhouse. Both have the workspace_name column filled with the value default_live. However, as long as there are not also records that are complete duplicates of these two records except with the value default in the workspace_name column, the pages will not appear in the Sulu admin CMS environment. Needless to say, they will also not appear on the public website proper.
Furthermore, when I let the DocumentManager in my controller action try to ->find my newly created document, I get a document of the class UnknownDocument. Hence, I cannot have the DocumentManager go ->publish on it; an Exception ensues. If I visit the pages in the Sulu admin environment, they are hence unpublished; once I publish them there, they can be found by the DocumentManager in the controller action - even if I later unpublish them. They are no longer UnknownDocument, for some reason. However, even if they can be found, I cannot have the DocumentManager go ->unpublish nor ->publish - that just has NO effect on the actual documents.
I was hoping there would be a Sulu cookbook-recipe or another piece of documentation that extensively describes how to create fully published pages dynamically, thus without going through the 'manual labor' of the actual CMS environment, but so far I haven't found one... All help is much appreciated :)
PS: For the purposes of being complete: we're running Sulu on a Windows server environment on PHP 7.1; dbase is PostgreSQL, Sulu being a local forked version of release tag 1.4.7 because I had to make some changes to the way Sulu handles uploaded files to get it to work on a Windows environment.
EDIT: a partial solution for making a new house page if none exists already (not explicitly using the AdminKernel, but should of course be run in a context where the AdminKernel is active):
public function getOrCreateHuisPagina(Huis $huis)
{
$parent = $this->documentManager->find('/cmf/immo/routes/nl/huizen', 'nl'); // This is indeed the route document for the "collector page" of all the houses, but this doesn't seem to give any problems (see below)
try {
$document = $this->documentManager->find('/cmf/immo/routes/nl/huizen/' . $huis->id(), 'nl'); // Here I'm checking whether the page already exists
} catch(DocumentNotFoundException $e) {
$document = $this->setupPublishedPage();
$document->setTitle($huis->naam());
$document->setStructureType('huis_detail');
$document->setResourceSegment('/huizen');
$document->setParent($parent);
$document->getStructure()->bind([
'title' => $huis->naam(), // Not sure if this is required seeing as I already set the title
'huis_id' => $huis->id(),
]);
$this->documentManager->persist(
$document,
'nl',
[
'parent_path' => '/cmf/immo/contents/huizen', // Explicit path to the content document of the parnt
]
);
}
$this->documentManager->publish($document, 'nl');
return $document;
}
First of all I think the following line does not load what you want it to load:
$parentHousesDocument = $documentManager->find('/cmf/immo/routes/nl/huizen', 'nl');
It loads the route instead of the page document, so it should look like the following:
$parentHousesDocument = $documentManager->find('/cmf/immo/contents/nl/huizen', 'nl');
Regarding your error with the URL, instead of overriding the StructureSubscriber you should simple use the setResourceSegment method of the document, which does exactly what you need :-)
And the default_live workspace is wrong, is it possible that you are running these commands on the website kernel? The thing is that the WebsiteKernel has the default_live workspace as default, and therefore writes the content in this workspace. If you run the command with the AdminKernel it should land in the default workspace, and you should be able to copy it into the default_live workspace with the publish method of the DocumentManager.
I hope that helps :-)

TFS 2012 Pull Custom Mapped Fields Programmatically

I came across this tutorial on how to programmatically pull Iteration Paths and Area Paths from TFS 2012 using their API.
http://geekswithblogs.net/TarunArora/archive/2011/07/10/tfs-2010-sdk-get-projects-iterations-area-path-queries-and.aspx
However, my company has set up some custom fields, one being "Team" (not to be confused with Team Projects that is already specified by TFS). In our Feature Request template, the user will click a dropdown menu and select the team they want to assign a project to. However, I haven't found a way to pull our team names programmatically like I can iteration paths and area paths.
Any ideas on how to pull custom fields?
You need something like this:
var server = tfs.GetService<WorkItemStore>();
var projectName = "MyTeamProject";
var workItemTypeName = "Bug";
var fieldName = "Field.RefName";
var allowedValues =
server
.Projects
.Cast<Project>()
.Single(project => project.Name == projectName)
.WorkItemTypes
.Cast<WorkItemType>()
.Single(workItemType => workItemType.Name == workItemTypeName)
.FieldDefinitions
.Cast<FieldDefinition>()
.Single(field => field.ReferenceName == fieldName)
.AllowedValues;

New & renamed workflows with existing content

I have a site with a custom content type Content, which initially had a single workflow attached, content_workflow. There are several thousand existing instances of Content.
I now have a need to add a second workflow to this type, content_beta_workflow. How can I update all existing content to be part of the new workflow?
On a related note: if I want rename the initial workflow to content_alpha_workflow, how can I update all existing content to reflect this change?
If you are simply changing from one workflow to the other, follow these steps:
Go to Site Setup > Types
Select your custom content type from the drop down menu, the page will update to display the current workflow
Select your new workflow from the dropdown, a map will be generated showing each state in the current workflow
For each state, select the state in your new workflow that most closely matches (or is most appropriate)
When you save, all objects of your custom site will be updated to use the new workflow. For each state in the map from the original workflow, existing content in that state will be put into the state you chose in step 4 above. Security settings will be re-indexed and you are done.
As for renaming the old workflow, you can do so in the portal_workflow tool in the ZMI. But only change the human-facing Title of the workflow. Changing the ID may have side effects for the workflow history of your content.
edited
Okay, I see from your comment that you are looking to add a new workflow to a type in addition to the one it already has. Here's a bit of sample code to accomplish that:
my_type = 'Content' # This is your content portal_type name
my_wf = 'content_workflow_beta'
wf_chain = list(wf_tool.getChainForPortalType(my_type))
if my_wf not in wf_chain:
wf_chain.append(my_wf)
wf_tool.setChainForPortalTypes([my_type], wf_chain)
You can add this code in an upgrade step for the package that defines your content type and workflows. Add a call to updateRoleMappings on the workflow tool and you'll be set to use the new workflow through the standard Plone UI in addition to your original workflow.
As you've already found, you can also manually update the workflow history of all objects to rename workflow ID, but that's a pretty invasive step.
As workflow_history is a dict property on each content item, it was a case of adding or updating suitable items as required. First, I copied the GenericSetup for content_workflow to content_alpha_workflow. Next, I created content_beta_workflow and added it to the profile. Then I wrote the following upgrade step:
import logging
from DateTime import DateTime
def modify_content_workflow_history(context, logger=None):
if logger is None: logger = logging.getLogger('my.product')
# import the new workflows
context.portal_setup.runImportStepFromProfile('profile-my.product:default', 'workflow')
# set up some defaults for the new records
_history_defaults = dict(
action = None,
actor = 'admin',
comments = 'automatically created by update v2',
time = DateTime(),
)
_alpha_defaults = dict(review_state = 'alpha_state_1', **_history_defaults)
_beta_defaults = dict(review_state = 'beta_state_1', **_history_defaults)
for parent in context.parents.values():
for content in parent.content.values():
# don't acquire the parent's history
if 'parent_workflow' in content.workflow_history:
content.workflow_history = {}
# copy content_workflow to content_alpha_workflow
if 'content_workflow' in content.workflow_history:
alpha_defaults = context.workflow_history['content_workflow']
del content.workflow_history['content_workflow']
else:
alpha_defaults = (_alpha_defaults,) # must be a tuple
content.workflow_history['ctcc_content_alpha_workflow'] = alpha_defaults
# create the beta workflow with a modified actor
beta_defaults = dict(**_beta_defaults)
beta_defaults['actor'] = u'%suser' % parent.id
content.workflow_history['ctcc_content_beta_workflow'] = (beta_defaults,)
logger.info('Content workflow history updated')