I want to copy a Classroom course with google app script. I can't find the method to do it.
I guess it would be something like "Classroom.Course.copy (....)"
can this be done?
Thanks
You can retrieve a Classroom and then create it with its parameters
Here's my approach:
First add the Advanced Service Classroom V1, then use the following code
function test() {
var course = Classroom.Courses.get("ID");
var newClass = {
name: course.name,
section: course.section,
descriptionHeading: course.descriptionHeading,
description: course.description,
room: course.room,
ownerId: course.ownerId,
courseState: course.courseState
};
Classroom.Courses.create(newClass)
}
If you are not sure about the Course ID simply list all your classrooms by using Method: courses.list.
Once you've done this, copy the writable values in order to create the new classroom.
NOTE: most of the courses fields are read-only, that's why there's no explicit method to copy the entire course
References
Resource: Course
Method: courses.create
Method: courses.get
Method: courses.list
Related
This feels basic, so I would expect to find this scenario mentioned, but I have searched and can't find an example that matches my scenario. I have 2 end points (I am using HTTP data sources) that I'm trying to combine.
Class:
{
id: string,
students: [
<studentID1>,
<studentID2>,
...
]
}
and Student:
{
id: String,
lastName: String
}
What I would like is a schema that looks like this:
Student: {
id: ID!
lastName: String
}
Class: {
id: ID!,
studentDetails: [Student]
}
From reading, I know that I need some sort of resolver on Class.studentDetails that will return an array/List of student objects. Most of the examples I have seen show retrieving the list of Students based on class ID (ctx.source.id), but that won't work in this case. I need to call the students endpoint 1 time per student, passing in the student ID (I cannot fetch the list of students by class ID).
Is there a way to write a resolver for Class/studentDetails that loops through the student IDs in Class and calls my students endpoint for each one?
I was thinking something like this in the Request Mapping Template:
#set($studentDetails = [])
#foreach($student in $ctx.source.students)
#util.qr(list.add(...invoke web service to get student details...))
#end
$studentDetails
Edit: After reading Lisa Shon's comment below, I realized that the batch resolver for DynamoDB data sources that does this, but I don't see a way to do that for HTTP data sources.
It's not ideal, but you can create an intermediate type.
type Student {
id: ID!
lastName: String
}
type Class {
id: ID!,
studentDetails: [StudentDetails]
}
type StudentDetails {
student: Student
}
In your resolver template for Class, create a list of those student ids
#foreach ($student in $class.students)
$util.qr($studentDetails.add({"id": "$student.id"}))
#end
and add it to your response object. Then, hook a resolver to the student field of StudentDetails and you will then be able to use $context.source.id for the individual student API call. Each id will be broken out of the array and be its own web request.
I opened a case with AWS Support and was told that the only way they know to do this is to create a Lambda Resolver that:
Takes an array of student IDs
Calls the students endpoint for each one
Returns an array of student details information
Instead of calling your student endpoint in the response, use a pipeline resolver and stitch the response from different steps using stash, context (prev.result/result), etc.
Right now, if I add a field to a Parse object and then save it, the new column shows up in the Parse dashboard.
For example, after running:
let media = new Parse.Object("Media");
media.set("foo", "bar");
await media.save();
I will have a new column called foo.
Is it possible to prevent this from happening?
Yes. This can be done using class-level permissions, which allow you to prevent fields being added to classes.
Parse lets you specify what operations are allowed per class. This lets you restrict the ways in which clients can access or modify your classes.
...
Add fields: Parse classes have schemas that are inferred when objects are created. While you’re developing your app, this is great, because you can add a new field to your object without having to make any changes on the backend. But once you ship your app, it’s very rare to need to add new fields to your classes automatically. You should pretty much always turn off this permission for all of your classes when you submit your app to the public.
You would have to add a beforeSave trigger for every one of your classes, keep a schema of all your keys, iterate over the request.object's keys, and see if there are any that do not belong in your schema. You can then either un-set them and call response.success(), or you can call response.error() to block the save entirely, preferably with a message indicating the offending field(s).
const approvedFields = ["field1", "field2", "field3"];
Parse.Cloud.beforeSave("MyClass", function(request, response) {
let object = request.object;
for( var key in object.dirtyKeys() ) {
if( approviedFields.indexOf(key) == -1 ) return response.error(`Error: Attempt to save invalid field: ${key});
}
response.success();
});
Edit:
Since this got a little attention, I thought I'd add that you can get the current schema of your class. From the docs: https://docs.parseplatform.org/js/guide/#schema
// create an instance to manage your class
const mySchema = new Parse.Schema('MyClass');
// gets the current schema data
mySchema.get();
It's not clear if that's async or not (you'll have to test yourself, feel free to comment update the answer once you know!)
However, once you have the schema, it has a fields property, which is an object. Check the link for what those look like.
You could validate an object by iterating over it's keys, and seeing if the schema.fields has that property:
Parse.Cloud.beforeSave('MyClass', (request, response) => {
let object = request.object;
for( var key in object.dirtyKeys() ) {
if( !schema.fields.hasOwnProperty(key) ) < Unset or return error >
}
response.success();
}
And an obligatory note for anyone just starting with Parse-Server on the latest version ,the request scheme has changed to no longer use a response object. You just return the result. So, keep that in mind.
Let's say I create a library shop and add to my bot :
dialogs/shop.js
var lib = new builder.Library('shop');
lib.dialog('car', function(session){})
module.exports.createLibrary = function () {
return lib.clone();
};
bot/index.js
bot.library(require('./dialogs/shop').createLibrary());
So I can trigger the car dialog through session.beginDialog('shop:car').
I'm looking to create a library for each category of items to shop so car comes within vehicle library and I get to call the car dialog through session.beginDialog('shop:vehicle:car')
I tried doing this :
var lib = new builder.Library('shop');
var lib_vehicle = new builder.Library('vehicle');
lib_vehicle.dialog('car', function(session){})
lib.library(lib_vehicle.clone());
module.exports.createLibrary = function () {
return lib.clone();
};
bot.library(require('./dialogs/shop').createLibrary());
But this triggers car dialog through session.beginDialog('vehicle:car') instead of session.beginDialog('shop:vehicle:car')
How do I achieve a hierarchical relationship between libraries?
Thanks
After looking at the source code, the SDK isn't set to handle hierarchical structures in this situation you described.
When dealing with 'vehicle:car', it generates an array and takes the library name and the dialog id. The session method findDialog() (called inside of beginDialog) is hardcoded to only accept one id, so 'car', and everything works fine. For 'shop:vehicle:car', 'car' is essentially left in the dust, and your chatbot goes to look for a dialog with an id of 'vehicle' in the library 'shop'.
Edit: If you think it's a feature worth exploring, file a [Feature Request] issue in the BotBuilder repo so a discussion can ensue.
Beginner question : I've worked through the Try Meteor tutorial. I've got fields in my HTML doc, backed by helper functions that reference collections, and BOOM --> the fields are updated when the data changes in the DB.
With the "Hide completed" checkbox, I've also seen data-binding to a session variable. The state of the checkbox is stored in the Session object by an event handler and BOOM --> the list view is updated "automatically" by its helper when this value changes. It seems a little odd to be assigning to a session object in a single page application.
Through all this, my js assigns nothing in global scope, I've created no objects, and I've mostly seen just pipeline code, getting values from one spot to another. The little conditional logic is sprayed about wherever it is needed.
THE QUESTION... Now I want to construct a model of my business data in javascript, modelling my business rules, and then bind html fields to this model. For example, I want to model a user, giving it an isVeryBusy property, and a rule that sets isVeryBusy=true if noTasks > 5. I want the property and the rule to be isolated in a "pure" business object, away from helpers, events, and the meteor user object. I want these business objects available everywhere, so I could make a restriction, say, to not assign tasks to users who are very busy, enforced on the server. I might also want a display rule to only display the first 100 chars of other peoples tasks if a user isVeryBusy. Where is the right place to create this user object, and how do I bind to it from my HTML?
You can (and probably should) use any package which allows you to attach a Schema to your models.
Have a look at:
https://github.com/aldeed/meteor-collection2
https://github.com/aldeed/meteor-simple-schema
By using a schema you can define fields, which are calculated based on other fields, see the autoValue property: https://github.com/aldeed/meteor-collection2#autovalue
Then you can do something like this:
// Schema definition of User
{
...,
isVeryBusy: {
type: Boolean,
autoValue: function() {
return this.tasks.length > 5;
}
},
...
}
For all your basic questions, I can strongly recommend to read the DiscoverMeteor Book (https://www.discovermeteor.com/). You can read it in like 1-2 days and it will explain all those basic questions in a really comprehensible way.
Best Regards,
There is a very good package to implement the solution you are looking for. It is created by David Burles and it's called "meteor-collection-helper". Here it the atmosphere link:
You should check the link to see the examples presented there but according to the description you could implement some of the functionality you mentioned like this:
// Define the collections
Clients = new Mongo.Collection('clients');
Tasks = new Mongo.Collection('tasks');
// Define the Clients collection helpers
Clients.helpers({
isVeryBusy: function(){
return this.tasks.length > 5;
}
});
// Now we can call it either on the client or on the server
if (Meteor.isClient){
var client = Clients.findOne({_id: 123});
if ( client.isVeryBusy() ) runSomeCode();
}
// Of course you can use them inside a Meteor Method.
Meteor.methods({
addTaskToClient: function(id, task){
var client = Clients.findOne({_id: id});
if (!client.isVeryBusy()){
task._client = id;
Tasks.insert(task, function(err, _id){
Clients.update({_id: client._id}, { $addToSet: { tasks: _id } });
});
}
}
});
// You can also refer to other collections inside the helpers
Tasks.helpers({
client: function(){
return Clients.findOne({_id: this._client});
}
});
You can see that inside the helper the context is the document transformed with all the methods you provided. Since Collections are ussually available to both the client and the server, you can access this functionality everywhere.
I hope this helps.
I am implementing ACL security using the spring-security-acl plugin. I have the following domain classes:
package test
class Subitem {
String name
static belongsTo = [employer: Employer]
static constraints = {
name blank: false
}
}
package test
class Employer {
String name
static hasMany = [users: User, items: Subitem]
static belongsTo = User
static constraints = {
name blank: false, unique: true
}
String toString() {
name
}
}
In the create.gsp file which is used to create a Subitem, there is the following statement:
<g:select id="employer" name="employer.id" from="${test.Employer.list()}" optionKey="id" required="" value="${subitemInstance?.employer?.id}" class="many-to-one"/>
From the EmployerController:
def list = {
params.max = Math.min(params.max ? params.int('max') : 10, 100)
[employerInstanceList: employerService.list(params),
employerInstanceTotal: employerService.count()]
}
Following the tutorial given here, I have moved some of the functionality with dealing with Employer to a service called EmployerService:
#PreAuthorize("hasRole('ROLE_USER')")
#PostFilter("hasPermission(filterObject, read)")
List<Employer> list(Map params) {
Employer.list params
}
int count() {
Employer.count()
}
Access to information in any given Employer class instance is restricted using ACL. At present, I can see ALL instances of Employer in the database in the drop down, and I assume that is because I am using the controller list(), not the service list() - however, I only want to see the filtered list of Employer domain classes. However, if I replace the g:select with:
<g:select id="employer" name="employer.id" from="${test.EmployerService.list()}" optionKey="id" required="" value="${subitemInstance?.employer?.id}" class="many-to-one"/>
then I get an internal server error because I haven't passed a Map parameter to the service list() function (and I don't know how to do this within the tag):
URI /security/subitem/create
Class groovy.lang.MissingMethodException
Message No signature of method: static test.EmployerService.list() is applicable for argument types: () values: [] Possible solutions: list(java.util.Map), is(java.lang.Object), wait(), find(), wait(long), get(long)
I only want to see the information that comes from the EmployerService list() function - how do I do this please? How do I reference the correct function from within the gap?
Edit 16 Mar 0835: Thanks #OverZealous, that's really helpful, I hadn't realised that. However, I've tried that and still get the same problem. I've put a println() statement in both the Employer and EmployerService list() functions, and can see that neither actually seems to get called when the g:select tag is parsed (even if I leave the g:select to refer to Employer). Is there another version of the list() function that is being called perhaps? Or how else to get the g:select to take account of the ACL?
Just change your method signature in the Service to look like this:
List<Employer> list(Map params = [:]) {
Employer.list params
}
The change is adding this: = [:]. This provides a default value for params, in this case, an empty map.
(This is a Groovy feature, BTW. You can use it on any method or closure where the arguments are optional, and you want to provide a default.)
OK, I worked it out, and here is the solution to anyone else who comes up against the same problem.
The create Subitem page is rendered by means of the Subitem's create.gsp file and the SubitemController. The trick is to amend the SubitemController create() closure:
class SubitemController {
def employerService
def create() {
// this line was the default supplied method:
// [subitemInstance: new Subitem(params)]
// so replace with the following:
params.max = Math.min(params.max ? params.int('max') : 10, 100)
[subitemInstance: new Subitem(params), employerInstanceList: employerService.list(params),
employerInstanceTotal: employerService.count()]
}
}
So now when the SubitemController is asked by the g:select within the Subitem view for the list of Employers, it calls the EmployerService, which supplies the correct answer. We have simply added 2 further variables that are returned to the view, and which can be referenced anywhere within the view (such as by the g:select tag).
The lesson for me is that the View interacts with the Controller, which can refer to a Service: the Service doesn't play nicely with a View, it seems.