WorkFlow Based in a Field - AppMaker - google-app-maker

I'm new with JavaScript and Google App Maker and I have a doubt with a little Workflow to Approve Expense.
I have two models:
Requests (Details of Request input by Widget)
| RequestedBy | TypeOfExpense | WhoApprove | WhoResponsableToExecute | Approved | Executed |
TypeOfExpense (Inputed by Admin via Widget)
| Type | ApproverOfExpense | RensableToExecute |
Scenario: A user creates a request choosing type of expense >> According Type >> Approver are notified - After approved >> RespnsableToExeute are notified - Affter Execution >> User is notified.
What feature I can use to associate (or trigger) in Request with ApproverOfExpense (from TypeOfExpense) and RensableToExecute (from TypeOfExpense) when an user input the Requisition of expense.
I'm think using onCreate events in Data Source but the app can are very slow.

I think onCreate is the right approach. It shouldn't make things noticeably slower as all it has to do is look up a single association, which is pretty fast. So basically what it sounds like you need to do is:
In the onCreate for request:
var email = record.TypeOfExpense.ApproverOfExpense;
// Email the approver.
And then maybe in OnSave for the request:
if (oldRecord.Approved === false && record.Approved === true) {
var email = record.TypeOfExpense.RensableToExecute;
// Email person who needs to execute
}

Related

Telegram Bot Python Inlinekeyboardmarkup does not work for all users in a group

I want to respond when someone sends the /start comand and display a text with a like/dislike button in a group.
Here is my sample code:
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import (
Updater,
CommandHandler,
CallbackQueryHandler,
ConversationHandler)
import logging
FIRST, SECOND = range(2)
keyboard = [[InlineKeyboardButton('👍', callback_data='0'),
InlineKeyboardButton('👎', callback_data='2')]]
def start(update, context):
reply_markup = InlineKeyboardMarkup(keyboard)
update.message.reply_text(
'Test Message\n',
reply_markup=reply_markup
)
return FIRST
def main():
updater = Updater(
'TOKEN', use_context=True)
dp = updater.dispatcher
conv_handler = ConversationHandler(
entry_points=[CommandHandler('start', start)],
states={
FIRST: [CallbackQueryHandler(a, pattern='^'+str(0)+'$'),
CallbackQueryHandler(b, pattern='^'+str(2)+'$')]
},
fallbacks=[CommandHandler('start', start)]
)
dp.add_handler(conv_handler)
updater.start_polling()
updater.idle()
if __name__ == "__main__":
main()
The callbacks only work for the user who sent the /start command. Other users cannot click the button or there is no callback. if another user sends a /start command, both like/dislike buttons of both posts of the bot work for the two users.
Where is the mistake?
I want every user to be able to press the buttons and it triggers a callback. regardless of whether the user has ever sent the /start command
I am thankful for every help.
The issue here is that by default conversations are per user - please also see this faq entry for details.
For your use case, I doubt that a ConversationHandler gives you any benefit. I would just register the CommandHandler and the CallbackQueryHandler independently.
Disclaimer: I'm currently the maintainer of python-telegram-bot.

Is it OK/recommended to skip the second call from the firebase authStateChanges()-Stream?

The firebase Authentication service offers a stream/event handler to get state changes on the auth status of the user (onAuthStateChanged), see
Authentication state # Flutter Fire docs
Get the currently signed-in user
According to the Firebase Android API docs + the question "Firebase Android onAuthStateChanged called twice", this stream/event handler is called when:
onAuthStateChanged(FirebaseAuth auth)
This method gets invoked in the UI thread on changes in the authentication state:
Right after the listener has been registered
When a user is signed in
When the current user is signed out
When the current user changes
A quick test showed:
firebaseAuth.authStateChanges().listen((currentUser) async {
if (prev != null) {
print("object compare");
print(prev == currentUser); // false
print(identical(prev, currentUser)); // false
print("============");
print(prev.uid == currentUser.uid); // true
print(prev.metadata.creationTime == currentUser.metadata.creationTime); // true
print(prev.metadata.lastSignInTime ==
currentUser.metadata.lastSignInTime); // true
print(prev.runtimeType == currentUser.runtimeType); // true
print(prev.hashCode == currentUser.hashCode); // false
print(prev.displayName == currentUser.displayName); // true
}
if (prev == null) {
prev = currentUser;
print("prev set");
}
}
Enough context. My question:
While setting up this stream, is it OK/recommended to
process the first event
skip the second event (e.g., with a $i counter)
continue every other incoming event from the 3rd on?
Why does this matter? What problem would it solve?
This question is mainly raised for performance reasons.
When an event is fired (and a user is logged in), I execute actions like
requesting a new/fresh token from firebase
listening to the firebase realtime db to receive updates for this specific user
Skipping events that come directly after another and do not contain new data, can save us API operations and would be beneficial for performance/processing reasons.
PS: I do not consider the question "Firebase Android onAuthStateChanged called twice" as a duplicate, because the answer from Fabricio is not marked as the correct one + from 2016 (things might have changed).

Scheduling complication updates

I have a custom complication on Apple Watch that I am trying to get to update once an hour. Each hour it should ping an API endpoint and if the data has changed from the last check, the complication should be updated.
Here is what I currently have that only seems to work once in a blue moon. When it DOES work, it does indeed ping my server and update the complication. It appears WatchOS just isn't calling my scheduled tasked once per hour. Is there a better standard practice that I'm missing?
#implementation ExtensionDelegate
- (void)applicationDidFinishLaunching {
// Perform any final initialization of your application.
[SessionManager sharedManager];
[self scheduleHourlyUpdate];
}
- (void) scheduleHourlyUpdate {
NSDate *nextHour = [[NSDate date] dateByAddingTimeInterval:(60 * 60)];
NSDateComponents *dateComponents = [[NSCalendar currentCalendar]
components: NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond fromDate:nextHour];
[[WKExtension sharedExtension] scheduleBackgroundRefreshWithPreferredDate:nextHour userInfo:nil scheduledCompletion:^(NSError * _Nullable error) {
// schedule another one in the next hour
if (error != nil)
NSLog(#"Error while scheduling background refresh task: %#", error.localizedDescription);
}];
}
- (void)handleBackgroundTasks:(NSSet<WKRefreshBackgroundTask *> *)backgroundTasks {
// Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one.
for (WKRefreshBackgroundTask * task in backgroundTasks) {
// Check the Class of each task to decide how to process it
if ([task isKindOfClass:[WKApplicationRefreshBackgroundTask class]]) {
// Be sure to complete the background task once you’re done.
WKApplicationRefreshBackgroundTask *backgroundTask = (WKApplicationRefreshBackgroundTask*)task;
[backgroundTask setTaskCompletedWithSnapshot:NO];
[self updateComplicationServer];
} else if ([task isKindOfClass:[WKSnapshotRefreshBackgroundTask class]]) {
// Snapshot tasks have a unique completion call, make sure to set your expiration date
WKSnapshotRefreshBackgroundTask *snapshotTask = (WKSnapshotRefreshBackgroundTask*)task;
[snapshotTask setTaskCompletedWithDefaultStateRestored:YES estimatedSnapshotExpiration:[NSDate distantFuture] userInfo:nil];
} else if ([task isKindOfClass:[WKWatchConnectivityRefreshBackgroundTask class]]) {
// Be sure to complete the background task once you’re done.
WKWatchConnectivityRefreshBackgroundTask *backgroundTask = (WKWatchConnectivityRefreshBackgroundTask*)task;
[backgroundTask setTaskCompletedWithSnapshot:NO];
} else if ([task isKindOfClass:[WKURLSessionRefreshBackgroundTask class]]) {
// Be sure to complete the background task once you’re done.
WKURLSessionRefreshBackgroundTask *backgroundTask = (WKURLSessionRefreshBackgroundTask*)task;
[backgroundTask setTaskCompletedWithSnapshot:NO];
} else if ([task isKindOfClass:[WKRelevantShortcutRefreshBackgroundTask class]]) {
// Be sure to complete the relevant-shortcut task once you’re done.
WKRelevantShortcutRefreshBackgroundTask *relevantShortcutTask = (WKRelevantShortcutRefreshBackgroundTask*)task;
[relevantShortcutTask setTaskCompletedWithSnapshot:NO];
} else if ([task isKindOfClass:[WKIntentDidRunRefreshBackgroundTask class]]) {
// Be sure to complete the intent-did-run task once you’re done.
WKIntentDidRunRefreshBackgroundTask *intentDidRunTask = (WKIntentDidRunRefreshBackgroundTask*)task;
[intentDidRunTask setTaskCompletedWithSnapshot:NO];
} else {
// make sure to complete unhandled task types
[task setTaskCompletedWithSnapshot:NO];
}
}
}
- (void)updateComplicationServer {
[self scheduleHourlyUpdate];
NSString *nsLogin = [NSUserDefaults.standardUserDefaults objectForKey:#"loginDTO"];
if (nsLogin != nil)
{
NSDateComponents *dateComponents = [[NSCalendar currentCalendar]
components: NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay fromDate:[NSDate date]];
LoginDTO *login = new LoginDTO([nsLogin cStringUsingEncoding:NSUTF8StringEncoding]);
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"https://www.myurl.com/Api/Watch/Complication"]];
[req setHTTPMethod:#"GET"];
// Set headers
[req addValue:[NSString stringWithUTF8String:login->GetApiKey()] forHTTPHeaderField:#"MySessionKey"];
[req addValue:[NSString stringWithFormat:#"%d,%d,%d", dateComponents.year, dateComponents.month, dateComponents.day] forHTTPHeaderField:#"FetchDate"];
[req addValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:req completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error)
{
// Call is complete and data has been received
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
if (httpResponse.statusCode == 200)
{
NSString* nsJson = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSString *prevJson = [NSUserDefaults.standardUserDefaults objectForKey:#"previousComplicationJson"];
if (prevComplicationJson != nil)
{
if ([prevComplicationJson isEqualToString:nsJson])
return; // Nothing changed, so don't update the UI.
}
// Update the dictionary
[NSUserDefaults.standardUserDefaults setObject:nsJson forKey:#"previousComplicationJson"];
CLKComplicationServer *server = [CLKComplicationServer sharedInstance];
for (int i = 0; i < server.activeComplications.count; i++)
[server reloadTimelineForComplication:server.activeComplications[i]];
}
}];
[task resume];
delete login;
}
}
watchOS background tasks are extraordinarily cumbersome to implement and debug, but based on Apple’s documentation and implementations that others have discussed, here‘s what I believe to be the best practice. I see a couple of issues here.
First, from the WKRefreshBackgroundTask docs:
The system suspends the extension as soon as all background tasks are complete.
Calling setTaskCompletedWithSnapshot on the task indicates to the system that you’ve finished performing all the work you need to do, so it will suspend your app. Your updateComplicationServer method is probably never getting a chance to run because the system suspends your extension too early.
More importantly, to make URL requests during a background update, you’ll need to use a background URL session. The example process outlined in the WKRefreshBackgroundTask docs shows the best practice for setting this up. In short:
You schedule a background refresh using WKExtension’s scheduleBackgroundRefresh just like you’re doing.
The system will wake your extension sometime after your preferred date (at the system’s discretion) with a WKRefreshBackgroundTask.
In your extension delegate’s handle method, check for the WKApplicationRefreshBackgroundTask; instead of performing the request with a URLSessionDataTask here, you need to schedule a background URL session so that the system can suspend your extension and perform the request on your behalf. See the WKURLSessionRefreshBackgroundTask docs for details about how background sessions should be set up.
The system will perform your URL request in a separate process, and again wake your extension once it has finished. It will call your extension delegate’s handle method like before, this time with a WKURLSessionRefreshBackgroundTask. Here, you need to do two things:
Save the background task in an instance variable on your extension delegate. We don’t want to set it complete just yet, but we need to keep it around to set complete later when the URL request finishes.
Create another background URL session using the background task’s sessionIdentifier, and use your extension delegate as the session’s delegate (why it doesn’t work to use another object as the delegate, I can’t say, but it seems to be a crucial detail). Note that using the same identifier to create a second URL session allows the system to connect the session to the download that it performed for you in another process; the purpose of this second background URL session is solely to connect the delegate with the session.
In the session delegate, implement both the urlSession(_ downloadTask: didFinishDownloadingTo:) and urlSession(task: didCompleteWithError:) functions.
Unlike your block-based NSURLSessionDataTask, background URL requests are always performed as download tasks. The system performs the request and gives you a temporary file with the resulting data. In the urlSession(_ downloadTask: didFinishDownloadingTo:) function, the data in that file and process it as needed to update your UI.
Finally, in the delegate’s urlSession(task: didCompleteWithError:) function, call setTaskCompletedWithSnapshot to tell the system that you’ve finished your work. Phew.
As I mentioned, this is all really frustrating to debug, mostly because it’s all up to the system when these things actually take place, if they happen at all. Apple’s documentation has this to say about the budget allocated to background refreshes:
In general, the system performs approximately one task per hour for each app in the dock (including the most recently used app). This budget is shared among all apps on the dock. The system performs multiple tasks an hour for each app with a complication on the active watch face. This budget is shared among all complications on the watch face. After you exhaust the budget, the system delays your requests until more time becomes available.
One final note: legend has it that the watchOS simulator doesn’t handle background URL refresh tasks properly, but unfortunately, Apple’s docs have no official word on that. Best to test on Apple Watch hardware if you‘re able.

ASAuthorizationAppleIDRequest with name and mail scope returns nil values

I'm implementing Sign in with Apple and noticed that the email and fullName properties of the returned ASAuthorizationAppleIDCredential are only filled on the very first Sign-In for this Apple ID. On all subsequent Sign-Ins those properties are nil.
Is this a bug on iOS 13 or expected behaviour?
Here is the code I'm using to start the request:
#available(iOS 13.0, *)
dynamic private func signInWithAppleClicked() {
let request = ASAuthorizationAppleIDProvider().createRequest()
request.requestedScopes = [.fullName, .email]
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
}
I'm receiving the credential in this delegate method:
public func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential else { return }
let userIdentifier = credential.user
let token = credential.identityToken
let authCode = credential.authorizationCode
let realUserStatus = credential.realUserStatus
let mail = credential.email // nil
let name = credential.fullName // nil
}
Seems like a bug but after reading different posts on apple forums it looks like this seems to be the expected behaviour.
So some takeaways.
On the first time sign in with apple (signup) make sure to create user account on your backend.
In case of any connection error with your servers, make sure you save the user details locally (because you are not getting this next time) and keep retrying to create account on your backend.
For testing on device you can revoke your apple ID login for your app. After revoking it will work like a signup next time and you will get the details like (email, name, etc).
To revoke access on your device with IOS 13.
iPhone Settings > Apple Id > Password & Security > Apple ID logins > {YOUR APP} > Stop using Apple ID
In case you're wondering how to retrieve email second and subsequent times, here's a hint: use identityToken which contains encoded in JWT user authorisation data including email.
Import this library to decode JWT: https://github.com/auth0/JWTDecode.swift
try this code
import JWTDecode
// ...
if let identityTokenData = appleIDCredential.identityToken,
let identityTokenString = String(data: identityTokenData, encoding: .utf8) {
print("Identity Token \(identityTokenString)")
do {
let jwt = try decode(jwt: identityTokenString)
let decodedBody = jwt.body as Dictionary<String, Any>
print(decodedBody)
print("Decoded email: "+(decodedBody["email"] as? String ?? "n/a") )
} catch {
print("decoding failed")
}
Or decode it at PHP backend like this:
print_r(json_decode(base64_decode(str_replace('_', '/', str_replace('-','+',explode('.', $identityTokenString)[1])))));
It is a correct behavior when implementing SignIn with Apple.
This behaves correctly, user info is only sent in the
ASAuthorizationAppleIDCredential upon initial user sign up. Subsequent
logins to your app using Sign In with Apple with the same account do
not share any user info and will only return a user identifier in the
ASAuthorizationAppleIDCredential. It is recommended that you securely
cache the initial ASAuthorizationAppleIDCredential containing the user
info until you can validate that an account has successfully been
created on your server.
To overcome this issue we can store all the required information in Keychain. I have created Singleton class for SignIn With Apple. I am sure it will help you.
Git source: https://github.com/IMHitesh/HSAppleSignIn
You need to follow below steps:
Step:1
Add the AppleSignIn folder into your project.
Step:2
Enable SignIn with Apple in Capabilities.
Step:3 -> IMPORTANT
Goto your UIViewController and Add Container view for SignIn with
apple.
if #available(iOS 13.0, *) {
appleSignIn.loginWithApple(view:viewAppleButton, completionBlock: { (userInfo, message) in
if let userInfo = userInfo{
print(userInfo.email)
print(userInfo.userid)
print(userInfo.firstName)
print(userInfo.lastName)
print(userInfo.fullName)
}else if let message = message{
print("Error Message: \(message)")
}else{
print("Unexpected error!")
}
})
}else{
viewAppleButton.isHidden = true
}
This seems to be the expected behaviour:
This behaves correctly, user info is only sent in the
ASAuthorizationAppleIDCredential upon initial user sign up. Subsequent
logins to your app using Sign In with Apple with the same account do
not share any user info and will only return a user identifier in the
ASAuthorizationAppleIDCredential. It is recommened that you securely
cache the initial ASAuthorizationAppleIDCredential containing the user
info until you can validate that an account has succesfully been
created on your server.
Source https://forums.developer.apple.com/thread/121496#379297
This is not bug but it will indicate that your authentication successfully store to your device setting.
if you want to that all information again then you need to following this states.
go to your device -> Settings -> Apple ID -> Password & Security
-> Apps Using your Apple ID -> you get list of apps used sign in with apple {find your app} -> swift left of your apps row {show Delete option} -> click on Delete
restart your app or repress sign in with apple button
Now you can get all information
In https://developer.apple.com/documentation/signinwithapplerestapi/authenticating_users_with_sign_in_with_apple it says:
Because the user’s information isn’t shared with your app in any subsequent API calls, your app should store it locally, immediately after you receive it from the API response. In case of subsequent failures in your process or network, you can read the information from local storage and try processing it again.
The email would be given on the first time sign in. If the user do not "revoke" the apple sign in of your app (which is in the user's Apple ID of system setting page) the callback for signing in would be returned with a nil email value. You could save the user id and email info of the first time sign-in successful result, and when the next time sign in to judge the difference between the return and the saved info.
A better practice is to judge the the value of ASAuthorizationAppleIDProvider.getCredentialState while your app is being "active" for syncing the sign-in state with back-end server in time.
Please refer to: How to Sign Out of Apple After Being Authenticated
I wrote a Helper class specific for this issue. This Helper class can help to save and retrieve the user info securely to and from keyChain.
I am using SwiftKeychainWrapper library to do the heavy task for me. Feel free to copy paste the helper class in your code.You might need to add any other extra information depending on your need.
import Foundation
import SwiftKeychainWrapper
/// A Helper class which abstract Keychain API related calls.
final class KeyChainService {
// MARK: - Properties
static let shared = KeyChainService()
/// Returns previous saved user name if available.
var appleUserName: String? {
return KeychainWrapper
.standard
.string(forKey: .appAppleUserName)
}
/// Returns previous saved user appleId/email if available.
var appleUserEmail: String? {
return KeychainWrapper
.standard
.string(forKey: .appAppleEmailId)
}
/// Saves the apple user name into keychain.
/// - Parameter name: Apple user name retrieved form AppleLogin.
/// - Returns: true if succeed otherwise false.
#discardableResult
func saveAppleUserName(name: String?) -> Bool {
guard let name = name else {return false}
return KeychainWrapper.standard.set(
name,
forKey: KeychainWrapper.Key.appAppleUserName.rawValue
)
}
/// Saves the apple user email into keychain.
/// - Parameter email: Apple userId/email retrieved form AppleLogin.
/// - Returns: true if succeed otherwise false.
#discardableResult
func saveAppleEmail(email: String?) -> Bool {
guard let email = email else {return false}
return KeychainWrapper.standard.set(
email,
forKey: KeychainWrapper.Key.appAppleEmailId.rawValue
)
}
/// Deletes both apple user name and saved Id from keyChain.
func deleteSavedAppleUserInfo(){
KeychainWrapper.standard.remove(forKey: .appAppleUserName)
KeychainWrapper.standard.remove(forKey: .appAppleEmailId)
}
}
// MARK: - KeychainWrapper + Extensions
extension KeychainWrapper.Key {
/// A random string used to identify saved user apple name from keychain.
static let appAppleUserName: KeychainWrapper.Key = "appAppleUserName"
/// A random string used to identify saved user apple email /Id from keychain.
static let appAppleEmailId:KeychainWrapper.Key = "appAppleEmailId"
}

Xively provisioning - how does an app request a device feed ID and key?

Xively provisioning - how does an app request a device feed ID and key?
On the "Provisioning" page it says:
"8 An application prompts the user for the serial number of the device they have just activated, the application uses a Master Key and the device serial number to request the device Feed ID and key from Xively."
This seems to suggest there's an API to do this, but I can't find it!
Does anyone know where this is in the docs or how to do this?
To get a feed ID for the device one need to make a Read Device request.
Go to Account | Settings | Add a master key
with Read/Update all permissions.
2) Note down the master key. Now to read all the devices (instances) created for a product (template) use API and key from (1)
URL: https://api.xively.com/v2/products/product_id/devices
X-ApiKey: The key you created in (1)
This API will return all devices with feedId and device keys.
A working example with Curl
include ('sensorui.inc');
include(APP_CLASS_LOADER);
// script to list all devices for a product
$url = "https://api.xively.com/v2/products/XwYNEGj4epo7HXNM0DGK/devices" ;
// API KEY
// This is master key API (from Account | settings page)
// for some reason (bug?) READ only key does not work!
$xheaders = array("X-ApiKey" => "your_master_key");
$cookies = array();
$curl = new \com\yuktix\util\curl\Wrapper($url,$cookies,$xheaders) ;
$curl->setCookies($cookies);
$curl->setXHeaders($xheaders);
// $curl->setDebug();
$response = $curl->doGet();
print_r($response);
$code = ($response["code"] != 200) ? 1 : 0 ;
return $code ;
Returns
rjha#kubuntu13:~/code/bitbucket/sensorui/scripts/xively$ php list-product.php
Array
(
[code] => 200
[response] => {"totalResults":2,"itemsPerPage":30,"startIndex":1,"devices":[{"serial":"SVSN001","activation_code":"xxx","created_at":"2014-02-02T15:05:37Z","activated_at":"2014-02-02T15:12:41Z","feed_id":xxx,"api_key":"xxx"},{"serial":"SVSN002","activation_code":"xxxx","created_at":"2014-02-02T15:05:37Z","activated_at":null,"feed_id":xxxx}]}

Resources