Slack Bolt App: Options body view state is not updated like actions body view state - slack

I am trying to implement dependent external selects inside a modal but I am having problems passing the state of the first dropdown to the second. I can see the state I need inside the app.action listener but I am not getting the same state inside the app.options listener.
body.view.state inside app.action("case_types"). I specifically need the case_create_case_type_block state.
"state": {
"values": {
"case_create_user_select_block": {
"case_create_selected_user": {
"type": "users_select",
"selected_user": "U01R3AE65GE"
}
},
"case_create_case_type_block": {
"case_types": {
"type": "external_select",
"selected_option": {
"text": { "type": "plain_text", "text": "Incident", "emoji": true },
"value": "Incident"
}
}
},
"case_create_case_subtype_block": {
"case_subtypes": { "type": "external_select", "selected_option": null }
},
"case_create_case_owner_block": {
"case_owners": { "type": "external_select", "selected_option": null }
},
"case_create_subject_block": {
"case_create_case_subject": {
"type": "plain_text_input",
"value": null
}
},
"case_create_description_block": {
"case_create_case_description": {
"type": "plain_text_input",
"value": null
}
}
}
},
body.view.state inside app.options("case_subtypes")
"state": {
"values": {
"case_create_user_select_block": {
"case_create_selected_user": {
"type": "users_select",
"selected_user": "U01R3AE65GE"
}
}
}
},
I did also try to update the view myself hoping it would update the state variables inside app.action({ action_id: "case_types" })
//need to update view with new values
try {
// Call views.update with the built-in client
const result = await client.views.update({
// Pass the view_id
view_id: body.view.id,
// Pass the current hash to avoid race conditions
hash: body.view.hash,
});
console.log("Case Type View Update result:");
console.log(JSON.stringify(result));
//await ack();
} catch (error) {
console.error(error);
//await ack();
}

I ended up posting this on the github issues page for slack bolt. This was a bug that will fixed in a future release. Below is the workaround using private metadata to hold the state to check for future dependent dropdowns.
// Action handler for case type
app.action('case_type_action_id', async ({ ack, body, client }) => {
try {
// Create a copy of the modal view template and update the private metadata
// with the selected case type from the first external select
const viewTemplate = JSON.parse(JSON.stringify(modalViewTemplate))
viewTemplate.private_metadata = JSON.stringify({
case_type: body.view.state.values['case_type_block_id']['case_type_action_id'].selected_option.value,
});
// Call views.update with the built-in client
const result = await client.views.update({
// Pass the view_id
view_id: body.view.id,
// Pass the current hash to avoid race conditions
hash: body.view.hash,
// Pass the updated view
view: viewTemplate,
});
console.log(JSON.stringify(result, 0, 2));
} catch (error) {
console.error(error);
}
await ack();
});
// Options handler for case subtype
app.options('case_subtype_action_id', async ({ body, options, ack }) => {
try {
// Get the private metadata that stores the selected case type
const privateMetadata = JSON.parse(body.view.private_metadata);
// Continue to render the case subtype options based on the case type
// ...
} catch (error) {
console.error(error);
}
});
See the full explaination here: https://github.com/slackapi/bolt-js/issues/1146

Related

route.params not visible inside useFocusEffect()

With React Navigation 5.x, params are passing back to parent function. Here is the code in parent receiving 2 params index and type:
import { useFocusEffect } from "#react-navigation/native"
export default MyWork = ({navigation, route}) => {
console.log("route.params :", route.params); //<<==route.params : {"index": 0, "type": "forsale"}}
....
useFocusEffect(
useCallback(() => {
console.log("in navigation", route.params); //<<== route.params undefined
try {
if (route.params?.index && route.params?.type) {
updateCnt(route.params.type, route.params.index);
}
} catch(err) {
}
}, []));
However inside the useFocusEffect, route.params becomes undefined. What is wrong here?
It looks like you need to add the route dependency to useCallback, otherwise it uses the old value (in this case undefined) rather than the updated one.
useFocusEffect(
useCallback(() => {
console.log('in navigation', route.params);
try {
if (route.params?.index && route.params?.type) {
updateCnt(route.params.type, route.params.index);
}
} catch (err) {
}
}, [route]),
);
A similar question was answered here where you can find more details https://stackoverflow.com/a/64190936/13912074

Is there any cost advantage of Parse.Object.saveAll vs. saving individually?

The Parse JS SDK provides a Parse.Object.saveAll() method to save many objects with one command.
From looking at ParseServerRESTController.js it seems that each object is saved individually:
if (path === '/batch') {
let initialPromise = Promise.resolve();
if (data.transaction === true) {
initialPromise = config.database.createTransactionalSession();
}
return initialPromise.then(() => {
const promises = data.requests.map(request => {
return handleRequest(
request.method,
request.path,
request.body,
options,
config
).then(
response => {
return {
success: response
};
},
error => {
return {
error: {
code: error.code,
error: error.message
},
};
}
);
});
return Promise.all(promises).then(result => {
if (data.transaction === true) {
if (
result.find(resultItem => typeof resultItem.error === 'object')
) {
return config.database.abortTransactionalSession().then(() => {
return Promise.reject(result);
});
} else {
return config.database.commitTransactionalSession().then(() => {
return result;
});
}
} else {
return result;
}
});
});
}
It seems that saveAll is merely a convenience wrapper around saving each object individually, so it still does seem to make n database requests for n objects.
It it correct that saveAll has no cost advantage (performance, network traffic, etc) vs. saving each object individually in Cloud Code?
I can tell you that the answer is that Parse.Object.saveAll and Parse.Object.destroyAll batch requests by default in batches of 20 objects. But why take my word for it? Let's test it out!
Turn verbose logging on and then run the following:
const run = async function run() {
const objects = [...Array(10).keys()].map(i => new Parse.Object('Test').set({i}));
await Parse.Object.saveAll(objects);
const promises = objects.map(o => o.increment('i').save());
return Promise.all(promises);
};
run()
.then(console.log)
.catch(console.error);
And here's the output from the parse-server logs (I've truncated it, but it should be enough to be apparent what is going on):
verbose: REQUEST for [POST] /parse/batch: { // <--- note the path
"requests": [ // <--- an array of requests!!!
{
"method": "POST",
"body": {
"i": 0
},
"path": "/parse/classes/Test"
},
... skip the next 7, you get the idea
{
"method": "POST",
"body": {
"i": 9
},
"path": "/parse/classes/Test"
}
]
}
.... // <-- remove some irrelevent output for brevity.
verbose: RESPONSE from [POST] /parse/batch: {
"response": [
{
"success": {
"objectId": "szVkuqURVq",
"createdAt": "2020-03-05T21:25:44.487Z"
}
},
...
{
"success": {
"objectId": "D18WB4Nsra",
"createdAt": "2020-03-05T21:25:44.491Z"
}
}
]
}
...
// now we iterate through and there's a request per object.
verbose: REQUEST for [PUT] /parse/classes/Test/szVkuqURVq: {
"i": {
"__op": "Increment",
"amount": 1
}
}
...
verbose: REQUEST for [PUT] /parse/classes/Test/HtIqDIsrX3: {
"i": {
"__op": "Increment",
"amount": 1
}
}
// and the responses...
verbose: RESPONSE from [PUT] /parse/classes/Test/szVkuqURVq: {
"response": {
"i": 1,
"updatedAt": "2020-03-05T21:25:44.714Z"
}
}
...
In the core manager code, you do correctly identify that we are making a request for each object to the data store (i.e. MongoDB), This is necessary because an object may have relations or pointers that have to be handled and that may require additional calls to the data store.
BUT! calls between the parse server and the data store are usually over very fast networks using a binary format, whereas calls between the client and the parse server are JSON and go over longer distances with ordinarily much slower connections.
There is one other potential advantage that you can see in the core manager code which is that the batch is done in a transaction.

how to return customize data in prisma subscription

I am learning graphql & prisma and I came across a question on prisma subscription.
I want to return an item list whenever there is a creation or update on Item. So this is my code which not works.
scheme.graphql
# import Item from "./generated/prisma.graphql"
type Subscription {
todoItems: TodoItems
}
type TodoItems {
items: [Item!]!
}
resolver
const Subscription = {
todoItems: {
subscribe: async (parent, args, context, info) => {
const itemSubscription = await context.db.subscription.item({
where: { mutation_in: ['CREATED', 'UPDATED'] },
}, info);
return itemSubscription;
},
resolve: async (payload, args, context, info) => {
const items = await context.db.query.items({ type: 0, orderBy: 'updatedAt_DESC' }, info);
return { items };
},
},
}
module.exports = {
Subscription,
}
and in graphql playground,
subscription{
todoItems{
items{
title
}
}
}
it gives the error:
{
"errors": [
{
"message": "Anonymous Subscription must select only one top level field.",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"todoItems"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"Error: Anonymous Subscription must select only one top level field.",
" at asErrorInstance (d:\\git\\inote\\node_modules\\graphql\\execution\\execute.js:489:43)",
" at <anonymous>",
" at process._tickCallback (internal/process/next_tick.js:118:7)"
]
}
}
}
]
}
Any idea?
Prisma does not support subscribing item lists. Instead, prisma wants you to subscribe to single item mutations ("created", "updated", "deleted"). As described here.
E.g.
subscription newTodos {
todo(where: {
mutation_in: [CREATED]
}) {
mutation
node {
title
}
}
}
To get "the full list", you have to query on the todos after subscribing to avoid missing events (race condition). As a result you have to manually "sync" the data from the subscription and your query.

I need help understanding Relay OutputFields, getFatQuery

This is the code from official docs of relay, This is for GraphQLAddTodoMutation
const GraphQLAddTodoMutation = mutationWithClientMutationId({
name: 'AddTodo',
inputFields: {
text: { type: new GraphQLNonNull(GraphQLString) },
},
outputFields: {
todoEdge: {
type: GraphQLTodoEdge,
resolve: ({localTodoId}) => {
const todo = getTodo(localTodoId);
return {
cursor: cursorForObjectInConnection(getTodos(), todo),
node: todo,
};
},
},
viewer: {
type: GraphQLUser,
resolve: () => getViewer(),
},
},
mutateAndGetPayload: ({text}) => {
const localTodoId = addTodo(text);
return {localTodoId};
},
});
I think mutateAndGetPayload executes first then outputFields? since it used localTodoId object as parameter, I see localTodoId object returned from mutateAndGetPayload.
and this is the code for relay mutation.please look at the getFatQuery
export default class AddTodoMutation extends Relay.Mutation {
static fragments = {
viewer: () => Relay.QL`
fragment on User {
id,
totalCount,
}
`,
};
getMutation() {
return Relay.QL`mutation{addTodo}`;
}
getFatQuery() {
return Relay.QL`
fragment on AddTodoPayload #relay(pattern: true) {
todoEdge,
viewer {
todos,
totalCount,
},
}
`;
}
getConfigs() {
return [{
type: 'RANGE_ADD',
parentName: 'viewer',
parentID: this.props.viewer.id,
connectionName: 'todos',
edgeName: 'todoEdge',
rangeBehaviors: ({status}) => {
if (status === 'completed') {
return 'ignore';
} else {
return 'append';
}
},
}];
}
getVariables() {
return {
text: this.props.text,
};
}
getOptimisticResponse() {
return {
// FIXME: totalCount gets updated optimistically, but this edge does not
// get added until the server responds
todoEdge: {
node: {
complete: false,
text: this.props.text,
},
},
viewer: {
id: this.props.viewer.id,
totalCount: this.props.viewer.totalCount + 1,
},
};
}
}
I think the todoEdge is from the outputFields from GraphQL? I see a viewer query on it, why does it need to query the viewer? How do I create a getFatQuery? I would really appreciate if someone help me understand this more and about Relay mutation.
mutateAndGetPayload executes then returns the payload to the outputFields
mutationWithClientMutationId
Source-Code
starWarsSchema example
mutationWithClientMutationId
inputFields: defines the input structures for mutation, where the input fields will be wraped with the input values
outputFields: defines the ouptput structure of the fields after the mutation is done which we can view and read
mutateAndGetPayload: this function is the core one to relay mutations, which performs the mutaion logic (such as database operations) and will return the payload to be exposed to output fields of the mutation.
mutateAndGetPayload maps from the input fields to the output fields using the mutation
operation. The first argument it receives is the list of the input parameters, which we can read to perform the mutation action
The object we return from mutateAndGetPayload can be accessed within the output fields
resolve() functions as the first argument.
getFatQuery() is where we represent, using a GraphQL fragment, everything
in our data model that could change as a result of this mutation

How to display mongoose error to backbone views

So, I have a backbone view where I am trying to save a user
this.model.save(user_details, { // this is backbone model
error: function (model, errors) {
},
success: function (model, response) {
}
});
Backbone Model urlRoot points to a backend function where
// here user is a Mongoose schema
user.save(function (err) {
if (err) {
res.send(err.errors);
}
});
I am running some validation in Mongoose schema.
If the validation fails how can I display these "err.errors" on my backbone view.
I can see at terminal if i console log the errors but not being able to send them back to the views.
Found out the solution after looking through the "errors" object
All errors are returned in "errors.resposeText" which has format like
{
"key name": {
"message": "",
"name": "",
"path": "",
"type": "",
"value": ""
}
}
this.model.save(user_details, { // this is backbone model
error: function (model, errors) {
var err = JSON.parse(errors.responseText);
$.each(errors, function (name, err) {
// do something with error
console.log(name + err.message);
}
},
success: function (model, response) {
}
});
NOTE: Errors from mongodb like unique, dup keys are not appended in this format. So its upto us to change them to json and wrap it up in res.errors.
In case of error in unique keys user.save(function (err) {
if (err) {
if(err.code!='undefined' && err.code=='11000')
err.errors = {'email':{'message':'This unique value is already in db'}};
res.send(500, err.errors);
}

Resources