I need to use a WCF API to save data into a DB. Ordinarily, I'd use chaining, like the example below:
IClientBroker clientBroker = UIContext.CreateWcfInterface<IClientBroker>("Data/ClientBroker.svc");
clientBroker.BeginSetClientBusinessName(_client.ID, businessName, (result) =>
{
_client = ((IClientBroker)result.AsyncState).EndSetClientBusinessName(result);
clientBroker.BeginSetClientAddress(_client.ID, addressObservableCollection, postcodeZip, (result2) =>
{
_client = ((IClientBroker)result2.AsyncState).EndSetClientAddress(result2);
clientBroker.BeginSetClientTelephone(_client.ID, telephone, (result3) =>
{
_client = ((IClientBroker)result3.AsyncState).EndSetClientTelephone(result3);
clientBroker.BeginSetClientFax(_client.ID, fax, (result4) =>
{
_client = ((IClientBroker)result4.AsyncState).EndSetClientFax(result4);
if (customFields.Save(validationSummaryBridge))
{
CloseWindow(true, "ClientID=" + _client.ID.ToString());
}
else
{
validationSummary.Errors.Add(new ValidationSummaryItem("Failed to save Custom Fields"));
}
}, clientBroker);
}, clientBroker);
}, clientBroker);
}, clientBroker);
}
This gives me faux-synchronous behaviour which I need so exceptions are thrown in a timely fashion and I can react on validation events.
This doesn't map well, however, when I have a loop of fields to save. For example, what pattern would be best to save the following list of "Custom Fields", where each Custom Field must be saved using a single WCF call?
ICustomFieldsBroker customFieldsBroker = UIContext.CreateWcfInterface<ICustomFieldsBroker>("Data/CustomFieldsBroker.svc");
foreach (CustomField customField in _customFields)
{
string newValue=_customFieldControlDictionary[customField].CustomField.Value;
customFieldsBroker.BeginSetCustomFieldValueForItem(DataTypeID, DataItemID, customField.Key, newValue, (result) =>
{
((ICustomFieldsBroker)result.AsyncState).EndSetCustomFieldValueForItem(result);
}, customFieldsBroker);
}
In the above example, this would just set off, say, 5 requests to the WCF API/threads which would potentially return AFTER the form has closed. I need them to "line up", so I can list their status and return to the form.
Thanks very much.
Don't let the WCF distract you, but if you have any comments, do let me know. :)
This is the answer I was looking for:
http://www.netfxharmonics.com/2008/11/Understanding-WCF-Services-in-Silverlight-2#WCFSilverlightThreadWaiting
Related
I'm using Bot Framework SDK with nodejs to implement a disamibuation flow.
I want that if two intents predicted by Luis are close to each other, ask the user from which of them are the one they want. I have done the validator but, I have a problem with the flow.
It is a waterfall Dialog with 3 steps:
FirstStep: Calls Orchestrator and Luis to get intents and entities. It pass the data with return await step.next({...})
Disamiguation Step: Checks if it is necessary to disambiguate, and, in that case, prompts the options. If not, it pass the data like the first step.
Answer step: If it has a disambiguation flag in the data it receives in step.result, it prompts the answer acordingly with the user response. Elsewhere, it uses the data in step.result that comes from the first step.
The problem is that, when it prompts user to say the intent, I lost the data of the FirstStep since I cannot use step.next({...})
¿How can I maintain both the data from the first step and the user answer in the prompt?
Here are the basic code:
async firstStep(step) {
logger.info(`FinalAnswer Dialog: firstStep`);
let model_dispatch = await this.bot.get_intent_dispatch(step.context);
let result = await this.bot.dispatchToTopIntentAsync(step.context, model_dispatch.model)
// model_dispatch = orchestrator_model
// result = {topIntent: String, entities: Array, disamibiguation: Array}
return await step.next({ model_dispatch: model_dispatch, result: result})
}
async disambiguationStep(step) {
logger.info(`FinalAnswer Dialog: disambiguationStep`);
if (step.result.result.disambiguation) {
logger.info("We need to disambiguate")
let disambiguation_options = step.result.result.disambiguation
const message_text = "What do you need";
const data = [
{
"title": "TEXT",
"value": disambiguation_option[0]
},
{
"title": "TEXT",
"value": disambiguation_option[1]
},
]
let buttons = data.map(function (d) {
return {
type: ActionTypes.PostBack,
title: d.title,
value: d.value
}
});
const msg = MessageFactory.suggestedActions(buttons, message_text);
return await step.prompt(TEXT_PROMPT, { prompt: msg });
return step.next(step.result) //not working
}
else {
logger.info("We dont desambiguate")
return step.next(step.result)
}
}
async answerStep(step) {
logger.info(`FinalAnswer Dialog: answerStep`);
let model_dispatch = step.result.model_dispatch
let result = step.result.result
//Show answer
return await step.endDialog();
}
You can use the step dictionary to store your values. The complex dialogs sample on GitHub is excellent for demonstrating this. https://github.com/microsoft/BotBuilder-Samples/blob/main/samples/javascript_nodejs/43.complex-dialog/dialogs/topLevelDialog.js
You can save data in the context with whatever name you want:
step.values['nameProperty'] = {}
This will be accessible within the entire execution context of the waterfall dialog:
const data = step.values['nameProperty'] // {}
New to react... Really banging my head against it with this one... I'm trying to figure out how to get a dynamically inserted component to update when the props are changed. I've assigned it to a parent state object but it doesn't seem to re-render. I've read that this is what's supposed to happen.
I was using ReactDOM.unmountComponentAtNode to re-render the specific elements I needed to, but it kept yelling at me with red text.
I need to hide "chat.message" unless the user has the authority to see it (server just sends empty string), but I still need to render the fact that it exists, and reveal it should the user get authentication. I'm using a css transition to reveal it, but I really need a good way to update the chat.message prop easily.
renderChats(uuid){
let userState = this.state.userStates.find(user => {
return user.uuid === uuid;
});
const children = userState.chats.map((chat) => {
let ChatReactElement = this.getChatMarkup(chat.cuid, chat.message, chat.status);
return ChatReactElement;
});
ReactDOM.render(children, document.getElementById(`chats-${this.state.guid}-${uuid}`));
}
getChatMarkup() just returns JSX and inserts Props... I feel like state should be getting passed along here. Even when I use a for-loop and insert the state explicitly, it doesn't seem to re-render on changes.
getChatMarkup(cuid, message, status){
return(
<BasicChatComponent
key={cuid}
cuid={cuid}
message={message}
status={status}
/>
);
}
I attempted to insert some code line this:
renderChats(uuid){
let userState = this.state.userStates.find(user => {
return user.uuid === uuid;
});
const children = userState.chats.map((chat) => {
let ChatReactElement = this.getChatMarkup(chat.cuid, chat.message, chat.status);
if(chat.status.hidden)
this.setState({ hiddenChatRE: [ ...this.state.hiddenChatRE, ChatReactElement ] }); // <== save elements
return ChatReactElement;
});
ReactDOM.render(children, document.getElementById(`chats-${this.state.guid}-${uuid}`));
}
and later in my code:
this.state.hiddenChatRE.every(ReactElement => {
if(ReactElement.key == basicChats[chatIndex].cuid){
ReactElement.props = {
... //completely invalid code
}
}
});
The only response I see here is my ReactDOM.unmountComponentAtNode(); approach...
Can anyone point me in the right direction here?
Although perhaps I should be kicking myself, I read up on how React deals with keys on their components. So there's actually a fairly trivial answer here if anyone comes looking... Just call your render function again after you update the state.
In my case, something like:
this.setState(state =>({
...state,
userStates : state.userStates.map((userstate) => {
if(userstate.uuid == basicChats[chatIndex].uuid) return {
...userstate,
chats: userstate.chats.map((chat) => {
if(chat.cuid == basicChats[chatIndex].cuid){
//
return {
cuid: basicChats[chatIndex].cuid,
message: basicChats[chatIndex].message,
status: basicChats[chatIndex].status
}
}
else return chat;
})
}
else return userstate;
})
}));
and then, elsewhere in my example:
this.state.userStates.map((userstate) => {
this.renderChats(userstate.uuid);
});
Other than the fact that I'd recommend using indexed arrays for this example to cut complexity, this is the solution, and works. This is because even though it feels like you'd end up with duplicates (that was my intuition), the uid on the BasicChatComponent itself makes all the difference, letting react know to only re-render those specific elements.
By default App Insights use page title as event name. Having dynamic page names, like "Order 32424", creates insane amount of event types.
Documentation on the matter says to use trackEvent method, but there are no examples.
appInsights.trackEvent("Edit button clicked", { "Source URL": "http://www.contoso.com/index" })
What is the best approach? It would be perfect to have some sort of map/filter which would allow to modify event name for some pages to the shared name, like "Order 23424" => "Order", at the same time to leave most pages as they are.
You should be able to leverage telemetry initializer approach to replace certain pattern in the event name with the more "common" version of that name.
Here is the example from Application Insights JS SDK GitHub on how to modify pageView's data before it's sent out. With the slight modification you may use it to change event names based on their appearance:
window.appInsights = appInsights;
...
// Add telemetry initializer
appInsights.queue.push(function () {
appInsights.context.addTelemetryInitializer(function (envelope) {
var telemetryItem = envelope.data.baseData;
// To check the telemetry item’s type:
if (envelope.name === Microsoft.ApplicationInsights.Telemetry.PageView.envelopeType) {
// this statement removes url from all page view documents
telemetryItem.url = "URL CENSORED";
}
// To set custom properties:
telemetryItem.properties = telemetryItem.properties || {};
telemetryItem.properties["globalProperty"] = "boo";
// To set custom metrics:
telemetryItem.measurements = telemetryItem.measurements || {};
telemetryItem.measurements["globalMetric"] = 100;
});
});
// end
...
appInsights.trackPageView();
appInsights.trackEvent(...);
With help of Dmitry Matveev I've came with the following final code:
var appInsights = window.appInsights;
if (appInsights && appInsights.queue) {
function adjustPageName(item) {
var name = item.name.replace("AppName", "");
if (name.indexOf("Order") !== -1)
return "Order";
if (name.indexOf("Product") !== -1)
return "Shop";
// And so on...
return name;
}
// Add telemetry initializer
appInsights.queue.push(function () {
appInsights.context.addTelemetryInitializer(function (envelope) {
var telemetryItem = envelope.data.baseData;
// To check the telemetry item’s type:
if (envelope.name === Microsoft.ApplicationInsights.Telemetry.PageView.envelopeType || envelope.name === Microsoft.ApplicationInsights.Telemetry.PageViewPerformance.envelopeType) {
// Do not track admin pages
if (telemetryItem.name.indexOf("Admin") !== -1)
return false;
telemetryItem.name = adjustPageName(telemetryItem);
}
});
});
}
Why this code is important? Because App Insights use page titles by default as Name for PageView, so you would have hundreds and thousands of different events, like "Order 123132" which would make further analysis (funnel, flows, events) meaningless.
Key highlights:
var name = item.name.replace("AppName", ""); If you put your App/Product name in title, you probably want to remove it from you event name, because it would just repeat itself everywhere.
appInsights && appInsights.queue you should check for appInsights.queue because for some reason it can be not defined and it would cause an error.
if (telemetryItem.name.indexOf("Admin") !== -1) return false; returning false will cause event to be not recorded at all. There certain events/pages you most likely do not want to track, like admin part of website.
There are two types of events which use page title as event name: PageView
and PageViewPerformance. It makes sense to modify both of them.
Here's one work-around, if you're using templates to render your /orders/12345 pages:
appInsights.trackPageView({name: TEMPLATE_NAME });
Another option, perhaps better suited for a SPA with react-router:
const Tracker = () => {
let {pathname} = useLocation();
pathname = pathname.replace(/([/]orders[/])([^/]+), "$1*"); // handle /orders/NN/whatever
pathname = pathname.replace(/([/]foo[/]bar[/])([^/]+)(.*)/, "$1*"); // handle /foo/bar/NN/whatever
useEffect(() => {
appInsights.trackPageView({uri: pathname});
}, [pathname]);
return null;
}
I am wondering if it is possible to fetch a job by requisitionId in Google Cloud Talent Solution. requisitionId has to be unique across jobs so it seems like a natural candidate for looking a job up.
When a job is created the api returns a job name that can be used to look the job up:
You can retrieve the details of a previously inserted job by sending a GET request to the Cloud Talent Solution. The URI should include the previously inserted job name returned by the original create request, as a URL parameter.
I'd like to avoid storing these names if possible. In my view storing them adds unnecessary complexity since I already have a unique requisitionId. To be clear the API does not let you add jobs with a duplicate requisitionId:
Job projects/{my_app_id}/jobs/{google_assigned_id} already exists. Request ID for tracking: ... Related Job requisition ID: ...
So can I look up jobs by requisitionId?
I could parse the error message that's returned to get the job name..but that seems pretty brittle.
It turns out the list method takes requisitionId so for a full, read-create-update cycle we can do:
const listRequest = {
parent: `projects/${projectId}`,
'filter': `companyName="${companyName}" AND requisitionId="${requisitionId}"`
}
const listResult = await jobService.projects.jobs.list(listRequest)
const existingJobs = listResult.data.jobs || [];
let existingJob = null
if (existingJobs && existingJobs.length > 0) {
existingJob = existingJobs[0]
}
let googleJob
if (!existingJob) {
const createRequest = {
'parent': `projects/${projectId}`,
'resource': {
'job': {
companyName,
requisitionId,
title,
description,
applicationInfo: {
emails: ['email#example.com']
}
}
}
}
googleJob = await jobService.projects.jobs.create(createRequest)
.then(result => result)
.catch(resp => {
console.error("ERROR")
console.error(resp)
})
} else {
const patchRequest = {
'name': existingJob.name,
'resource': {
'job': {
companyName,
requisitionId,
title,
description,
applicationInfo: {
emails: ['email#example.com']
}
}
}
}
googleJob = await jobService.projects.jobs.patch(patchRequest)
.then(result => result)
.catch(resp => {
console.error("ERROR")
console.error(resp)
})
}
Docs: https://cloud.google.com/talent-solution/job-search/docs/reference/rest/v3/projects.jobs/list?authuser=0&hl=de
Notes:
The double quotes in the filter parameter are important. It will not accept single quotes and will give a cryptic error message.
The patch request cannot take a parent parameter even though everything else requires a parent parameter...
one can add it as custom attribute:
Map<String, CustomAttribute> attributes = new HashMap<>();
attributes
.put("requisitionId", new CustomAttribute().setStringValue(requisitionId)
.setFilterable(true));
Job job = new Job()
...
.setCustomAttributes(attributes);
Job jobCreated = createJob(job);
String jobName = jobCreated.getName();
and then search for requisitionId with a custom attribute filter:
JobQuery jobQuery = new JobQuery().setCustomAttributeFilter(filter);
this is a little redundant, but JobQuery has no method .setRequisitionId().
here's the documentation.
I am building a bot with the botbuilder framework using node, and I am now trying to use the CardAction.dialogAction:
builder.CardAction.dialogAction(session, 'help', 'topic:mytopic', 'Click me')
It generates a message that looks like this:
action?help=topic:mytopic
Now I need to route that action to the correct dialog to handle it, but I can't figure out where and how to do that. Seeing as this is a builtin feature, I figured there should be easy ways of doing this already?
Appreciate the help.
For a lack of any other options at this point I resorted to writing my own simple action recognizer. It is no beauty, but it does the trick.
function action_recognizer() {
return {
recognize: function (context, done) {
let intent = { score: 0.0 }
const text = context.message.text
if (text) {
const m = text.match(/action\?(\w+)=?(.+)/)
logger.debug(m)
if (m) {
switch (m[1]) {
case 'help':
intent = { score: 1.0, intent: 'help' }
if (m.length > 2) {
intent.entities = [{entity: m[2], type: 'custom_intent'}]
}
break
}
}
}
done(null, intent)
}
}
}
This will basically direct to my help:/ dialog. Which in turn will read the entities list (if custom_intent types are available).