A Demo Project for the UnrealEngineSDK
Loading...
Searching...
No Matches
ApexAPI.cpp
Go to the documentation of this file.
1// Copyright 2022 PixoVR Corp. All Rights Reserved.
2
3
4#include "ApexAPI.h"
6#include "ApexSDK.h"
7#include "ApexSDKSettings.h"
8#include "HeadMountedDisplayFunctionLibrary.h"
9#include "GenericPlatform/GenericPlatformMisc.h"
10
12
13#define LogAPEX(pmt, ...) UE_LOG(LogApexAPI, Log, TEXT(pmt), ##__VA_ARGS__)
14#define WarnAPEX(pmt, ...) UE_LOG(LogApexAPI, Warning, TEXT(pmt), ##__VA_ARGS__)
15#define ErrorAPEX(pmt, ...) UE_LOG(LogApexAPI, Error, TEXT(pmt), ##__VA_ARGS__)
16#define FatalAPEX(pmt, ...) UE_LOG(LogApexAPI, Fatal, TEXT(pmt), ##__VA_ARGS__)
17
20 , ApexSettings(nullptr)
21{
22}
23
25{
27 {
29 }
30
31 if (ApexSettings != nullptr)
32 {
34
35 if (URL.IsEmpty())
36 {
37 FatalAPEX("No URL available. Please set the Server URI in the settings.");
38 }
39
40 URL.RemoveFromEnd("/");
41 if (!URL.Contains("https://"))
42 {
43 if (URL.Contains("http://"))
44 {
45 URL = URL.Replace(TEXT("http://"), TEXT(""));
46 }
47
48 URL.InsertAt(0, "https://");
49 }
50
52
54 if (!WebSocketURL.IsEmpty())
55 {
56 WebSocketURL.RemoveFromEnd("/");
57 if (!WebSocketURL.Contains("wss://"))
58 {
59 FString URLBody, URLScheme;
60 if (WebSocketURL.Split("://", &URLScheme, &URLBody))
61 {
62 WebSocketURL = URLBody;
63 }
64
65 WebSocketURL = "wss://" + WebSocketURL;
66 }
67
68 if (!WebSocketURL.EndsWith("/ws"))
69 {
70 WebSocketURL = WebSocketURL + "/ws";
71 }
72
73 WebSocketEnabled = true;
74 }
75 else
76 {
77 WebSocketEnabled = false;
78 }
79
82 LogAPEX("Launching module %d version %s", LoadedModuleId, *ModuleVersion);
83 }
84}
85
86void UApexAPI::Initialize(FSubsystemCollectionBase& Collection)
87{
88 Super::Initialize(Collection);
89
90 VaRestSubsystem = GEngine->GetEngineSubsystem<UVaRestSubsystem>();
91
93
94 DeviceModel = UHeadMountedDisplayFunctionLibrary::GetHMDDeviceName().ToString();
95 // Tag PC launches specifically if there is no HMD available.
96 if (DeviceModel.Equals("None", ESearchCase::IgnoreCase))
97 {
98 DeviceModel = "PC";
99 }
100 DeviceId = FGenericPlatformMisc::GetDeviceId();
101 if (DeviceId.IsEmpty())
102 {
103 DeviceId = FGenericPlatformMisc::GetDeviceMakeAndModel();
104 }
105
107 {
108 ApexSocket = MakeUnique<ApexWebSocket>();
109
110 ApexSocket->OnApexWebSocketConnected().AddLambda([&]() -> void {OnWebSocketConnected.Broadcast(); OnStaticWebSocketConnected.Broadcast(); });
111 ApexSocket->OnApexWebSocketConnectFailed().AddLambda([&](const FString& Error) -> void {OnWebSocketConnectFailed.Broadcast(Error); OnStaticWebSocketConnectFailed.Broadcast(Error); });
112 ApexSocket->OnApexWebSocketDisconnected().AddLambda([&](int32 StatusCode, const FString& Reason, bool bWasClean) -> void {OnWebSocketDisconnected.Broadcast(StatusCode, Reason, bWasClean); OnStaticWebSocketDisconnected.Broadcast(StatusCode, Reason, bWasClean); });
113 ApexSocket->OnApexAuthCodeRetrieved().AddLambda([&](const FString& AuthorizationCode) -> void {OnAuthorizationCodeRetrieved.Broadcast(AuthorizationCode); OnStaticAuthorizationCodeRetrieved.Broadcast(AuthorizationCode); });
114 ApexSocket->OnApexAuthenticateComplete().AddLambda([&](UVaRestJsonObject* LoginJsonObject) -> void { HandleLogin(LoginJsonObject); });
115
116 ApexSocket->AttemptConnect(WebSocketURL);
117 }
118}
119
121{
123 {
124 return ApexSocket->IsWebsocketConnected();
125 }
126
127 return false;
128}
129
130
132{
134 {
135 if (!ApexSocket->AttemptConnect(WebSocketURL))
136 {
137 WarnAPEX("Web Socket already connected.");
138 }
139 }
140}
141
143{
145 {
146 ApexSocket->RequestAuthorizationCode();
147 }
148}
149
151{
153 FString RequestURL = URL + "/ping";
154 Request->SetURL(RequestURL);
155
156 Request->OnStaticRequestComplete.AddLambda([&](UVaRestRequestJSON* Request) -> void { OnPingComplete(Request); });
157 Request->OnStaticRequestFail.AddLambda([&](UVaRestRequestJSON* Request) -> void { OnPingFail(Request); });
158
159 Request->ExecuteProcessRequest();
160}
161
163{
164 LogAPEX("On Ping Completed!");
165 OnRequestComplete.Broadcast(EApexRequestType::Ping, Request);
167}
168
170{
171 LogAPEX("On Ping Failed!");
172
173 UVaRestJsonObject* ResponseObject = Request->GetResponseObject();
174 FAPEXRequestFailed APEXFailedRequest;
175
176 APEXFailedRequest.FromJsonObject(ResponseObject->GetRootObject());
177
178 OnRequestFail.Broadcast(EApexRequestType::Ping, Request, APEXFailedRequest);
180}
181
182void UApexAPI::Login(const FApexLoginInfo& InLoginInfo, bool bShouldLogout)
183{
184 if (bShouldLogout && CurrentActiveLogin.IsSet())
185 {
186 Logout();
187 }
188
190 FString RequestURL = URL + "/login";
191 Request->SetURL(RequestURL);
192
194 LoginJson->SetField("login", VaRestSubsystem->ConstructJsonValueString(InLoginInfo.Username));
195 LoginJson->SetField("password", VaRestSubsystem->ConstructJsonValueString(InLoginInfo.Password));
196
197 Request->SetRequestObject(LoginJson);
198
199 Request->OnStaticRequestComplete.AddLambda([&](UVaRestRequestJSON* Request) -> void { OnLoginComplete(Request); });
200 Request->OnStaticRequestFail.AddLambda([&](UVaRestRequestJSON* Request) -> void { OnLoginFail(Request); });
201
202 Request->ExecuteProcessRequest();
203}
204
206{
207 // We have to handle errors that show up as valid messages.
208 bool FailedResponse = true;
209 if (Request->GetResponseCode() == 200)
210 {
211 if (!Request->GetResponseObject()->GetRootObject()->HasField("Error"))
212 {
213 FailedResponse = false;
214 HandleLogin(Request->GetResponseObject());
215 }
216 }
217
218 if (FailedResponse)
219 {
220 OnLoginFail(Request);
221 }
222}
223
225{
226 LogAPEX("Login failed with response code %i", Request->GetResponseCode());
227
228 UVaRestJsonObject* ResponseObject = Request->GetResponseObject();
229 FAPEXRequestFailed APEXFailedRequest;
230
231 APEXFailedRequest.FromJsonObject(ResponseObject->GetRootObject());
232
233 OnRequestFail.Broadcast(EApexRequestType::Login, Request, APEXFailedRequest);
235}
236
237
239{
240 LogAPEX("Login Completed!");
242 {
243 WarnAPEX("Replacing the current login.");
245 }
246
248
250}
251
253{
254 const UVaRestRequestJSON* DummyRequestJSON = VaRestSubsystem->ConstructVaRestRequest();
256 {
257 // TODO MGruber - Uncomment once the logout endpoint is added to the v2 of the platform API.
258 /*UVaRestRequestJSON* Request = VaRestSubsystem->ConstructVaRestRequestExt(EVaRestRequestVerb::POST, EVaRestRequestContentType::json);
259 FString RequestURL = URL + "/logout";
260 Request->SetURL(RequestURL);
261
262 UVaRestJsonObject* LogoutJson = VaRestSubsystem->ConstructVaRestJsonObject();
263 LogoutJson->SetField("id", VaRestSubsystem->ConstructJsonValueNumber(CurrentActiveLogin.ID));
264
265 Request->SetRequestObject(LogoutJson);
266
267 Request->OnStaticRequestComplete.AddLambda([&](UVaRestRequestJSON* Request) -> void { OnLogoutComplete(Request); });
268 Request->OnStaticRequestFail.AddLambda([&](UVaRestRequestJSON* Request) -> void { OnLogoutFail(Request); });
269
270 Request->ExecuteProcessRequest();*/
271
273
274 OnRequestComplete.Broadcast(EApexRequestType::Logout, DummyRequestJSON);
275 OnStaticRequestComplete.Broadcast(EApexRequestType::Logout, DummyRequestJSON);
276 }
277 else
278 {
279 FAPEXRequestFailed APEXFailedRequest;
280 APEXFailedRequest.Error = true;
281 APEXFailedRequest.Message = "No user currently logged in.";
282
283 UVaRestJsonObject* ResponseObject = DummyRequestJSON->GetResponseObject();
284 ResponseObject->SetBoolField("Error", APEXFailedRequest.Error);
285 ResponseObject->SetStringField("Message", APEXFailedRequest.Message);
286
287 OnRequestFail.Broadcast(EApexRequestType::Logout, DummyRequestJSON, APEXFailedRequest);
288 OnStaticRequestFail.Broadcast(EApexRequestType::Logout, DummyRequestJSON);
289 }
290}
291
293{
294 bool FailedResponse = true;
295 if (Request->GetResponseCode() == 200)
296 {
297 if (!Request->GetResponseObject()->GetRootObject()->HasField("Error"))
298 {
301 }
302 }
303
304 if (FailedResponse)
305 {
306 OnLogoutFail(Request);
307 }
308}
309
311{
312 LogAPEX("Logout failed with response code %i", Request->GetResponseCode());
313
314 UVaRestJsonObject* ResponseObject = Request->GetResponseObject();
315 FAPEXRequestFailed APEXFailedRequest;
316
317 APEXFailedRequest.FromJsonObject(ResponseObject->GetRootObject());
318
319 OnRequestFail.Broadcast(EApexRequestType::Logout, Request, APEXFailedRequest);
321}
322
324{
326
327 FStringFormatOrderedArguments FormatArguments;
328 FormatArguments.Add(FStringFormatArg(URL));
329 FormatArguments.Add(FStringFormatArg(CurrentActiveLogin.ID));
330 FormatArguments.Add(FStringFormatArg(LoadedModuleId));
331 FString AccessURL = FString::Format(TEXT("{0}/access/user/{1}/module/{2}"), FormatArguments);
332 Request->SetURL(AccessURL);
333 Request->SetHeader("Authorization: Bearer", CurrentActiveLogin.SessionToken);
334
335 Request->OnStaticRequestComplete.AddLambda([&](UVaRestRequestJSON* Request) -> void { OnUserVerificationComplete(Request); });
336 Request->OnStaticRequestFail.AddLambda([&](UVaRestRequestJSON* Request) -> void { OnUserVerificationFail(Request); });
337
338 Request->ExecuteProcessRequest();
339}
340
342{
343 bool FailedResponse = true;
344 if (Request->GetResponseCode() == 200)
345 {
346 UVaRestJsonObject* ResponseObject = Request->GetResponseObject();
347 if (!ResponseObject->GetRootObject()->HasField("Error"))
348 {
349 LogAPEX("User Verification Completed!");
350
351 if (ResponseObject->HasField("access"))
352 {
353 FailedResponse = !ResponseObject->GetBoolField("access");
354 }
355
356 if (FailedResponse == false)
357 {
358 OnRequestComplete.Broadcast(EApexRequestType::Login, Request);
360 }
361 }
362 }
363
364 if (FailedResponse)
365 {
366 OnUserVerificationFail(Request);
367 }
368}
369
371{
372 LogAPEX("User module access verification failed with response code %i", Request->GetResponseCode());
373
374 UVaRestJsonObject* ResponseObject = Request->GetResponseObject();
375 FAPEXRequestFailed APEXFailedRequest;
376 if (Request->GetResponseCode() == 200)
377 {
378 if (ResponseObject->GetRootObject()->HasField("access"))
379 {
380 APEXFailedRequest.Error = true;
381 APEXFailedRequest.Message = "User does not have access to this module.";
382 }
383 else
384 {
385 APEXFailedRequest.FromJsonObject(ResponseObject->GetRootObject());
386 }
387 }
388
389 OnRequestFail.Broadcast(EApexRequestType::Login, Request, APEXFailedRequest);
391}
392
393bool UApexAPI::JoinSession(FString InScenarioId, const FXAPIExtension& InContextExtension)
394{
396 {
397 ErrorAPEX("Cannot join session with no active login.");
398 return false;
399 }
400
401 if (!InScenarioId.IsEmpty())
402 {
403 ScenarioId = InScenarioId;
404 }
405 else
406 {
407 ScenarioId = TEXT("<Unavailable>");
408 }
409
410 if (SessionInProgress == true)
411 {
412 ErrorAPEX("Session is already in progress. The previous session didn't complete or a new session was started during an active session.");
413 }
414
415 CurrentSessionGuid = FGuid::NewGuid();
416
417 // Finish filling this out
418 FXAPIStatement Statement;
421 Actor.Name = FString::Printf(TEXT("%s %s"), *CurrentActiveLogin.FirstName, *CurrentActiveLogin.LastName);
422
423 FXAPIVerb Verb;
425 Verb.Display.Add("en", "Joined Session");
426
427 FXAPIActivity Activity;
428 FStringFormatOrderedArguments IdFormat({ LoadedModuleId, ScenarioId });
429 Activity.ID = FString::Format(TEXT("https://pixovr.com/xapi/objects/{0}/{1}"), IdFormat);
430
431 FXAPIContext Context;
433 Context.Revision = ModuleVersion;
434 Context.Platform = Platform;
435
436 FXAPIExtension ContextExtension;
437 if (!InContextExtension.IsEmpty())
438 {
439 ContextExtension.Clone(InContextExtension);
440 }
441
443 Context.Extensions.AddString("device_id", DeviceId);
444 Context.Extensions.AddString("device_model", DeviceModel);
445
446 Statement.Actor = Actor;
447 Statement.Verb = Verb;
448 Statement.Target = Activity;
449 Statement.Context = Context;
450
451 FJoinSessionData JoinData;
452 JoinData.DeviceId = DeviceId;
453 JoinData.IpAddress = "0.0.0.0"; // We should get rid of this as there isn't a good way to get external IP addresses
454 JoinData.ModuleId = LoadedModuleId;
455 JoinData.Uuid = CurrentSessionGuid.ToString();
457 JoinData.JsonData = Statement;
458
460 FString RequestURL = URL + "/event";
461 Request->SetURL(RequestURL);
462 Request->SetHeader("Authorization: Bearer", CurrentActiveLogin.SessionToken);
463
465 JoinSessionJson->SetRootObject(JoinData.ToJsonObject());
466
467 Request->SetRequestObject(JoinSessionJson);
468
469 Request->OnStaticRequestComplete.AddLambda([&](UVaRestRequestJSON* Request) -> void { OnJoinSessionComplete(Request); });
470 Request->OnStaticRequestFail.AddLambda([&](UVaRestRequestJSON* Request) -> void { OnJoinSessionFail(Request); });
471
472 Request->ExecuteProcessRequest();
473
474 return true;
475}
476
478{
479 LogAPEX("Session was successfully joined.");
480 SessionInProgress = true;
483}
484
486{
487 LogAPEX("Failed to join session. %d", Request->GetResponseCode());
488 SessionInProgress = false;
489 CurrentSessionGuid.Invalidate();
490
491 UVaRestJsonObject* ResponseObject = Request->GetResponseObject();
492 FAPEXRequestFailed APEXFailedRequest;
493
494 APEXFailedRequest.FromJsonObject(ResponseObject->GetRootObject());
495
496 OnRequestFail.Broadcast(EApexRequestType::JoinSession, Request, APEXFailedRequest);
498}
499
500bool UApexAPI::CompleteSession(const FSessionData& InSessionData)
501{
503 {
504 ErrorAPEX("Cannot complete session with no active login.");
505 return false;
506 }
507
508 if (SessionInProgress == false)
509 {
510 ErrorAPEX("No current session in progress.");
511 return false;
512 }
513
514 // Create our actor
517 Actor.Name = FString::Printf(TEXT("%s %s"), *CurrentActiveLogin.FirstName, *CurrentActiveLogin.LastName);
518
519 // Create our verb
520 FXAPIVerb Verb;
522 Verb.Display.Add("en", "Completed Session");
523
524 // Create the session activity
525 FXAPIActivity Activity;
526 Activity.ID = FString::Printf(TEXT("https://pixovr.com/xapi/objects/%d/%s"), LoadedModuleId, *ScenarioId);
527
528 // Create our context
529 FXAPIContext Context;
531 Context.Revision = ModuleVersion;
532 Context.Platform = Platform;
533
534 // Build an extension for the context
535 Context.Extensions = InSessionData.AdditionalContextData;
537 Context.Extensions.AddString("device_id", DeviceId);
538 Context.Extensions.AddString("device_model", DeviceModel);
539
540 // Create our results
541 FXAPIResult Result;
542 Result.Completion = InSessionData.Complete;
543 Result.Success = InSessionData.Success;
544 // Add score to the results
545 Result.Score.Min = InSessionData.ScoreMin;
546 Result.Score.Max = InSessionData.ScoreMax;
547 Result.Score.Raw = InSessionData.Score;
548 Result.Score.Scaled = InSessionData.ScoreScaled;
549 Result.Duration = FTimespan::FromSeconds(InSessionData.Duration);
550 Result.Extensions = InSessionData.AdditionalResultData;
551
552 // Create our statement and add the pieces
553 FXAPIStatement Statement;
554 Statement.Actor = Actor;
555 Statement.Verb = Verb;
556 Statement.Target = Activity;
557 Statement.Context = Context;
558 Statement.Result = Result;
559
560 FCompleteSessionData SessionData;
561 SessionData.DeviceId = DeviceId;
562 SessionData.ModuleId = LoadedModuleId;
563 SessionData.Uuid = CurrentSessionGuid.ToString();
565 SessionData.JsonData = Statement;
566 SessionData.SessionData = InSessionData;
567
568 // Now we create our request and send it!
570 FString RequestURL = URL + "/event";
571 Request->SetURL(RequestURL);
572 Request->SetHeader("Authorization: Bearer", CurrentActiveLogin.SessionToken);
573
575 CompleteSessionJson->SetRootObject(SessionData.ToJsonObject());
576
577 Request->SetRequestObject(CompleteSessionJson);
578
579 Request->OnStaticRequestComplete.AddLambda([&](UVaRestRequestJSON* Request) -> void { OnCompleteSessionComplete(Request); });
580 Request->OnStaticRequestFail.AddLambda([&](UVaRestRequestJSON* Request) -> void { OnCompleteSessionFail(Request); });
581
582 Request->ExecuteProcessRequest();
583
584 return true;
585}
586
588{
589 LogAPEX("Session complete!");
590 SessionInProgress = false;
591 CurrentSessionGuid.Invalidate();
594}
595
597{
598 WarnAPEX("Failed to complete session. %d", Request->GetResponseCode());
599
600 UVaRestJsonObject* ResponseObject = Request->GetResponseObject();
601 FAPEXRequestFailed APEXFailedRequest;
602
603 APEXFailedRequest.FromJsonObject(ResponseObject->GetRootObject());
604
605 OnRequestFail.Broadcast(EApexRequestType::CompleteSession, Request, APEXFailedRequest);
607}
608
610{
611 FXAPIStatement EventStatement = InStatement;
613 {
614 ErrorAPEX("Cannot complete session with no active login.");
615 return false;
616 }
617
618 if (SessionInProgress == false)
619 {
620 ErrorAPEX("No current session in progress.");
621 return false;
622 }
623
624 if (EventStatement.IsEmpty())
625 {
626 ErrorAPEX("No event data to send.");
627 return false;
628 }
629
630 if (!EventStatement.Actor.IsEmpty())
631 {
632 WarnAPEX("Actor data should not be filled out.");
633 }
634
635 if (EventStatement.Verb.IsEmpty())
636 {
637 ErrorAPEX("Verb missing from eventStatement.");
638 return false;
639 }
640
641 if (EventStatement.Verb.ID.IsEmpty())
642 {
643 ErrorAPEX("Verb ID missing from eventStatement.");
644 return false;
645 }
646
647 if (EventStatement.Target.IsEmpty())
648 {
649 ErrorAPEX("Object (target) missing from eventStatement.");
650 return false;
651 }
652
653 EventStatement.Actor.MBox = CurrentActiveLogin.Email;
654 EventStatement.Actor.Name = FString::Printf(TEXT("%s %s"), *CurrentActiveLogin.FirstName, *CurrentActiveLogin.LastName);
655
656 EventStatement.Context.Registration = CurrentSessionGuid;
657 EventStatement.Context.Revision = ModuleVersion;
658 EventStatement.Context.Platform = Platform;
659
660 EventStatement.Context.Extensions.AddString("device_id", DeviceId);
661 EventStatement.Context.Extensions.AddString("device_model", DeviceModel);
662
665 Event.ModuleId = LoadedModuleId;
666 Event.Uuid = CurrentSessionGuid.ToString();
668 Event.JsonData = InStatement;
669
670 // Now we create our request and send it!
672 FString RequestURL = URL + "/event";
673 Request->SetURL(RequestURL);
674 Request->SetHeader("Authorization: Bearer", CurrentActiveLogin.SessionToken);
675
677 CompleteSessionJson->SetRootObject(Event.ToJsonObject());
678
679 Request->SetRequestObject(CompleteSessionJson);
680
681 Request->OnStaticRequestComplete.AddLambda([&](UVaRestRequestJSON* Request) -> void { OnSendSessionEventComplete(Request); });
682 Request->OnStaticRequestFail.AddLambda([&](UVaRestRequestJSON* Request) -> void { OnSendSessionEventFail(Request); });
683
684 Request->ExecuteProcessRequest();
685
686 return true;
687}
688
690{
691 LogAPEX("Session event was sent successfully.");
694}
695
697{
698 WarnAPEX("Failed to send session event. %d", Request->GetResponseCode());
699
700 UVaRestJsonObject* ResponseObject = Request->GetResponseObject();
701 FAPEXRequestFailed APEXFailedRequest;
702
703 APEXFailedRequest.FromJsonObject(ResponseObject->GetRootObject());
704
705 OnRequestFail.Broadcast(EApexRequestType::SessionEvent, Request, APEXFailedRequest);
707}
708
709bool UApexAPI::FetchTopTen(FString Scenario, int OrgUnit)
710{
711 return FetchTopTen(-1, -1.f, Scenario, OrgUnit);
712}
713
715{
717}
718
719bool UApexAPI::FetchTopTenByUser(int UserId, FString Scenario, int OrgUnit)
720{
721 return FetchTopTen(UserId, -1.f, Scenario, OrgUnit);
722}
723
724bool UApexAPI::FetchTopTenByTimeRange(float TimePriorToToday, FString Scenario, int OrgUnit)
725{
726 return FetchTopTen(-1, TimePriorToToday, Scenario, OrgUnit);
727}
728
729bool UApexAPI::FetchTopTen(int UserId, float TimePriorToToday, FString Scenario, int OrgUnit)
730{
732 {
733 ErrorAPEX("Cannot complete session with no active login.");
734 return false;
735 }
736
738 FString URLFormat = "{url}/analytics/top10/module/{moduleId}";
739 FStringFormatNamedArguments FormatArguments;
740
741 if (UserId >= 0)
742 {
743 URLFormat += "/user/{userId}";
744 }
745
746 FString DataQueryFormat = "?";
747
748 if (!Scenario.IsEmpty())
749 {
750 DataQueryFormat += "scenario={moduleId}/{scenario}&";
751 }
752
753 if (TimePriorToToday > 0.0f)
754 {
755 DataQueryFormat += "dateRange={dateRange}&";
756 }
757
758 if (OrgUnit >= 0)
759 {
760 DataQueryFormat += "orgUnit={orgUnit}&";
761 }
762
763 if (DataQueryFormat.EndsWith("&"))
764 {
765 DataQueryFormat.RemoveFromEnd("&");
766 }
767
768 URLFormat += DataQueryFormat;
769
770 FormatArguments.Add("url", FStringFormatArg(URL));
771 FormatArguments.Add("moduleId", FStringFormatArg(LoadedModuleId));
772 FormatArguments.Add("userId", FStringFormatArg(UserId));
773 FormatArguments.Add("dateRange", FStringFormatArg(TimePriorToToday));
774 FormatArguments.Add("scenario", FStringFormatArg(Scenario));
775 FormatArguments.Add("orgUnit", FStringFormatArg(OrgUnit));
776 FString AnalyticsURL = FString::Format(*URLFormat, FormatArguments);
777 Request->SetURL(AnalyticsURL);
778 Request->SetHeader("Authorization: Bearer", CurrentActiveLogin.SessionToken);
779
780 Request->OnStaticRequestComplete.AddLambda([&](UVaRestRequestJSON* Request) -> void { OnTopTenComplete(Request); });
781 Request->OnStaticRequestFail.AddLambda([&](UVaRestRequestJSON* Request) -> void { OnTopTenFail(Request); });
782
783 Request->ExecuteProcessRequest();
784
785 return true;
786}
787
789{
790 bool FailedResponse = true;
791 if (Request->GetResponseCode() == 200)
792 {
793 UVaRestJsonObject* ResponseObject = Request->GetResponseObject();
794 if (ResponseObject->HasField("Error"))
795 {
796 if (!ResponseObject->GetBoolField("Error"))
797 {
798 LogAPEX("Successfully fetched top ten data!");
799
800 // It's not a failed response!
801 FailedResponse = false;
802
803 FTopTenData FetchedTopTenData;
804 FetchedTopTenData.FromJsonObject(ResponseObject->GetRootObject());
805
806 OnFetchTopTenComplete.Broadcast(Request, FetchedTopTenData);
807 OnStaticFetchTopTenComplete.Broadcast(Request, FetchedTopTenData);
808 }
809 }
810 }
811
812 if (FailedResponse)
813 {
814 OnTopTenFail(Request);
815 }
816}
817
819{
820 LogAPEX("Fetch Top Ten failed with response code %i", Request->GetResponseCode());
821
822 UVaRestJsonObject* ResponseObject = Request->GetResponseObject();
823 FAPEXRequestFailed APEXFailedRequest;
824
825 APEXFailedRequest.FromJsonObject(ResponseObject->GetRootObject());
826
827 OnFetchTopTenFail.Broadcast(Request, APEXFailedRequest);
828 OnStaticFetchTopTenFail.Broadcast(Request, APEXFailedRequest);
829}
830
831#undef FatalAPEX
832#undef ErrorAPEX
833#undef WarnAPEX
834#undef LogAPEX
#define WarnAPEX(pmt,...)
Definition ApexAPI.cpp:14
DEFINE_LOG_CATEGORY_STATIC(LogApexAPI, Log, All)
#define LogAPEX(pmt,...)
Definition ApexAPI.cpp:13
#define ErrorAPEX(pmt,...)
Definition ApexAPI.cpp:15
#define FatalAPEX(pmt,...)
Definition ApexAPI.cpp:16
@ Login
Definition ApexAPI.h:21
@ Logout
Definition ApexAPI.h:22
@ CompleteSession
Definition ApexAPI.h:25
@ Ping
Definition ApexAPI.h:20
@ JoinSession
Definition ApexAPI.h:23
@ SessionEvent
Definition ApexAPI.h:24
@ AuthorizationCode
static bool IsAvailable()
Definition ApexSDK.h:32
UApexSDKSettings * GetSettings() const
Definition ApexSDK.cpp:59
static FApexSDKModule & Get()
Definition ApexSDK.h:22
bool SendSessionEvent(const FXAPIStatement &InStatement)
UFUNCTION(BlueprintCallable, Category = "Apex|API")
Definition ApexAPI.cpp:609
bool FetchTopTenByTimeRange(float TimePriorToToday, FString Scenario, int OrgUnit)
UFUNCTION(BlueprintCallable, Category = "Apex|API")
Definition ApexAPI.cpp:724
void OnLogoutComplete(const class UVaRestRequestJSON *Request)
Definition ApexAPI.cpp:292
bool CompleteSession(const FSessionData &InSessionData)
UFUNCTION(BlueprintCallable, Category = "Apex|API")
Definition ApexAPI.cpp:500
bool WebSocketEnabled
UPROPERTY(BlueprintReadOnly)
Definition ApexAPI.h:164
class UVaRestSubsystem * VaRestSubsystem
Definition ApexAPI.h:278
FOnStaticApexRequestFail OnStaticRequestFail
Definition ApexAPI.h:303
bool JoinSession(FString InScenarioId, const FXAPIExtension &InContextExtension)
UFUNCTION(BlueprintCallable, Category = "Apex|API")
Definition ApexAPI.cpp:393
void VerifyModuleAcess()
Definition ApexAPI.cpp:323
FString Platform
UPROPERTY(BlueprintReadOnly)
Definition ApexAPI.h:119
void Ping()
UFUNCTION(BlueprintCallable, Category = "Apex|API")
Definition ApexAPI.cpp:150
bool FetchTopTenByUser(int UserId, FString Scenario, int OrgUnit)
UFUNCTION(BlueprintCallable, Category = "Apex|API", meta=(DisplayName = "Fetch Top Ten By User"))
Definition ApexAPI.cpp:719
bool SessionInProgress
UPROPERTY(BlueprintReadOnly)
Definition ApexAPI.h:158
FOnStaticApexRequestComplete OnStaticRequestComplete
Definition ApexAPI.h:302
void Login(const FApexLoginInfo &InLoginInfo, bool bShouldLogout=true)
UFUNCTION(BlueprintCallable, Category = "Apex|API")
Definition ApexAPI.cpp:182
void OnTopTenFail(const class UVaRestRequestJSON *Request)
Definition ApexAPI.cpp:818
FOnStaticApexAuthCodeRetrieved OnStaticAuthorizationCodeRetrieved
Definition ApexAPI.h:353
void OnLoginComplete(const class UVaRestRequestJSON *Request)
Definition ApexAPI.cpp:205
void OnJoinSessionComplete(const class UVaRestRequestJSON *Request)
Definition ApexAPI.cpp:477
bool IsWebSocketConnected()
UFUNCTION(BlueprintCallable, Category = "Apex|API")
Definition ApexAPI.cpp:120
FOnApexRequestComplete OnRequestComplete
UPROPERTY(BlueprintAssignable, Category = "Apex|Event")
Definition ApexAPI.h:294
void HandleLogin(class UVaRestJsonObject *LoginJsonObject)
Definition ApexAPI.cpp:238
void ConnectWebSocketService()
UFUNCTION(BlueprintCallable, Category = "Apex|API", meta = (Tooltip = "This function only needs to be...
Definition ApexAPI.cpp:131
FGuid CurrentSessionGuid
UPROPERTY(BlueprintReadOnly)
Definition ApexAPI.h:143
void OnJoinSessionFail(const class UVaRestRequestJSON *Request)
Definition ApexAPI.cpp:485
FString ModuleVersion
UPROPERTY(BlueprintReadOnly)
Definition ApexAPI.h:113
FOnApexTopTenDataFail OnFetchTopTenFail
UPROPERTY(BlueprintAssignable, Category = "Apex|Event")
Definition ApexAPI.h:316
FOnApexWebSocketConnectFailed OnWebSocketConnectFailed
UPROPERTY(BlueprintAssignable, Category = "Apex|Event")
Definition ApexAPI.h:333
FOnApexRequestFail OnRequestFail
UPROPERTY(BlueprintAssignable, Category = "Apex|Event")
Definition ApexAPI.h:300
void SetupModuleSettings()
Definition ApexAPI.cpp:24
FOnApexWebSocketConnected OnWebSocketConnected
UPROPERTY(BlueprintAssignable, Category = "Apex|Event")
Definition ApexAPI.h:327
void OnSendSessionEventFail(const class UVaRestRequestJSON *Request)
Definition ApexAPI.cpp:696
void OnPingFail(const class UVaRestRequestJSON *Request)
Definition ApexAPI.cpp:169
void OnLogoutFail(const class UVaRestRequestJSON *Request)
Definition ApexAPI.cpp:310
void OnSendSessionEventComplete(const class UVaRestRequestJSON *Request)
Definition ApexAPI.cpp:689
FOnStaticApexWebSocketDisconnected OnStaticWebSocketDisconnected
Definition ApexAPI.h:351
void RequestAuthorizationCode()
UFUNCTION(BlueprintCallable, Category = "Apex|API")
Definition ApexAPI.cpp:142
FOnApexTopTenDataComplete OnFetchTopTenComplete
UPROPERTY(BlueprintAssignable, Category = "Apex|Event")
Definition ApexAPI.h:310
virtual void Initialize(FSubsystemCollectionBase &Collection) override
Definition ApexAPI.cpp:86
void Logout()
UFUNCTION(BlueprintCallable, Category = "Apex|API")
Definition ApexAPI.cpp:252
FString WebSocketURL
The API Websocket URL.
Definition ApexAPI.h:95
FString ScenarioId
UPROPERTY(BlueprintReadOnly)
Definition ApexAPI.h:101
FOnStaticApexTopTenDataFail OnStaticFetchTopTenFail
Definition ApexAPI.h:320
TUniquePtr< ApexWebSocket > ApexSocket
Definition ApexAPI.h:283
FUserSessionData CurrentActiveLogin
UPROPERTY(BlueprintReadOnly)
Definition ApexAPI.h:149
bool FetchTopTen(FString Scenario, int OrgUnit)
UFUNCTION(BlueprintCallable, Category = "Apex|API")
Definition ApexAPI.cpp:709
class UApexSDKSettings * ApexSettings
Definition ApexAPI.h:279
FOnStaticApexTopTenDataComplete OnStaticFetchTopTenComplete
Definition ApexAPI.h:318
void OnUserVerificationComplete(const class UVaRestRequestJSON *Request)
Definition ApexAPI.cpp:341
FString DeviceModel
UPROPERTY(BlueprintReadOnly)
Definition ApexAPI.h:131
FOnApexAuthCodeRetrieved OnAuthorizationCodeRetrieved
UPROPERTY(BlueprintAssignable, Category = "Apex|Event")
Definition ApexAPI.h:345
void OnTopTenComplete(const class UVaRestRequestJSON *Request)
Definition ApexAPI.cpp:788
FString URL
The API URL.
Definition ApexAPI.h:84
void OnPingComplete(const class UVaRestRequestJSON *Request)
Definition ApexAPI.cpp:162
int LoadedModuleId
UPROPERTY(BlueprintReadOnly, Meta=(DisplayName="Module Id"))
Definition ApexAPI.h:107
FOnApexWebSocketDisconnected OnWebSocketDisconnected
UPROPERTY(BlueprintAssignable, Category = "Apex|Event")
Definition ApexAPI.h:339
FString DeviceId
UPROPERTY(BlueprintReadOnly)
Definition ApexAPI.h:125
void OnUserVerificationFail(const class UVaRestRequestJSON *Request)
Definition ApexAPI.cpp:370
void OnLoginFail(const class UVaRestRequestJSON *Request)
Definition ApexAPI.cpp:224
void OnCompleteSessionFail(const class UVaRestRequestJSON *Request)
Definition ApexAPI.cpp:596
FOnStaticApexWebSocketConnectFailed OnStaticWebSocketConnectFailed
Definition ApexAPI.h:349
FOnStaticApexWebSocketConnected OnStaticWebSocketConnected
Definition ApexAPI.h:347
void OnCompleteSessionComplete(const class UVaRestRequestJSON *Request)
Definition ApexAPI.cpp:587
bool FetchTopTenByCurrentUser(FString Scenario)
UFUNCTION(BlueprintCallable, Category = "Apex|API", meta = (DisplayName = "Fetch Top Ten By User"))
Definition ApexAPI.cpp:714
FString ModuleVersion
UPROPERTY(Config, EditAnywhere, Category = "Apex", meta = (DisplayName = "Module Version"))
int ModuleId
UPROPERTY(Config, EditAnywhere, Category = "Apex", meta = (DisplayName = "Module ID"))
FString ServerURI
UPROPERTY(Config, EditAnywhere, Category = "Apex")
UCLASS(BlueprintType, Blueprintable)
TSharedRef< FJsonObject > & GetRootObject()
void SetRootObject(const TSharedPtr< FJsonObject > &JsonObject)
void SetStringField(const FString &FieldName, const FString &StringValue)
UFUNCTION(BlueprintCallable, Category = "VaRest|Json")
void SetBoolField(const FString &FieldName, bool InValue)
UFUNCTION(BlueprintCallable, Category = "VaRest|Json")
bool HasField(const FString &FieldName) const
UFUNCTION(BlueprintCallable, Category = "VaRest|Json")
void SetField(const FString &FieldName, UVaRestJsonValue *JsonValue)
UFUNCTION(BlueprintCallable, Category = "VaRest|Json")
bool GetBoolField(const FString &FieldName) const
UFUNCTION(BlueprintCallable, Category = "VaRest|Json")
UCLASS(BlueprintType, Blueprintable)
void SetRequestObject(UVaRestJsonObject *JsonObject)
UFUNCTION(BlueprintCallable, Category = "VaRest|Request")
UVaRestJsonObject * GetResponseObject() const
UFUNCTION(BlueprintCallable, Category = "VaRest|Response")
void SetHeader(const FString &HeaderName, const FString &HeaderValue)
UFUNCTION(BlueprintCallable, Category = "VaRest|Request")
int32 GetResponseCode() const
UFUNCTION(BlueprintPure, Category = "VaRest|Response")
void SetURL(const FString &Url=TEXT("http://alyamkin.com"))
UFUNCTION(BlueprintCallable, Category = "VaRest|Request")
FOnStaticRequestFail OnStaticRequestFail
virtual void ExecuteProcessRequest()
UFUNCTION(BlueprintCallable, Category = "VaRest|Request")
FOnStaticRequestComplete OnStaticRequestComplete
UVaRestJsonValue * ConstructJsonValueString(const FString &StringValue)
UFUNCTION(BlueprintPure, meta = (DisplayName = "Construct Json String Value"), Category = "VaRest|Sub...
UVaRestRequestJSON * ConstructVaRestRequest()
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Construct Json Request (Empty)"),...
UVaRestRequestJSON * ConstructVaRestRequestExt(EVaRestRequestVerb Verb, EVaRestRequestContentType ContentType)
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Construct Json Request"), Category = "VaRest|Subs...
UVaRestJsonObject * ConstructVaRestJsonObject()
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Construct Json Object"), Category = "VaRest|Subsy...
static const FString PIXOVR_SESSION_JOINED
static const FString PIXOVR_SESSION_EVENT
static const FString PIXOVR_SESSION_COMPLETE
static const FString MODULE_IDS
static const FString COMPLETED_SESSION
static const FString JOINED_SESSION
USTRUCT(BlueprintType)
Definition ApexTypes.h:134
FString Message
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|Failed Request")
Definition ApexTypes.h:156
bool Error
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|Failed Request")
Definition ApexTypes.h:142
virtual void FromJsonObject(const TSharedPtr< FJsonObject > &JObject) override
Definition ApexTypes.h:166
USTRUCT(BlueprintType)
Definition ApexTypes.h:16
FString Password
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|Login")
Definition ApexTypes.h:31
FString Username
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|Login")
Definition ApexTypes.h:24
USTRUCT(BlueprintType)
Definition ApexTypes.h:272
FString Uuid
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|Complete Session")
Definition ApexTypes.h:280
int ModuleId
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|Complete Session")
Definition ApexTypes.h:292
FString DeviceId
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|Complete Session")
Definition ApexTypes.h:298
FXAPIStatement JsonData
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|Complete Session")
Definition ApexTypes.h:304
FString EventType
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|Complete Session")
Definition ApexTypes.h:286
virtual TSharedPtr< FJsonObject > ToJsonObject() const override
Definition ApexTypes.h:340
FSessionData SessionData
Definition ApexTypes.h:307
USTRUCT(BlueprintType)
Definition ApexTypes.h:51
virtual TSharedPtr< FJsonObject > ToJsonObject() const
Definition ApexTypes.h:93
FString IpAddress
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|Join Session")
Definition ApexTypes.h:83
FString DeviceId
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|Join Session")
Definition ApexTypes.h:77
int ModuleId
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|Join Session")
Definition ApexTypes.h:71
FXAPIStatement JsonData
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|Join Session")
Definition ApexTypes.h:89
FString Uuid
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|Join Session")
Definition ApexTypes.h:59
FString EventType
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|Join Session")
Definition ApexTypes.h:65
USTRUCT(BlueprintType)
Definition ApexTypes.h:184
bool Complete
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|Session Data")
Definition ApexTypes.h:192
float Score
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|Session Data")
Definition ApexTypes.h:204
float ScoreMin
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|Session Data")
Definition ApexTypes.h:216
float ScoreScaled
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|Session Data")
Definition ApexTypes.h:210
float ScoreMax
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|Session Data")
Definition ApexTypes.h:222
int Duration
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|Session Data")
Definition ApexTypes.h:228
FXAPIExtension AdditionalContextData
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|Session Data")
Definition ApexTypes.h:235
FXAPIExtension AdditionalResultData
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|Session Data")
Definition ApexTypes.h:241
bool Success
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|Session Data")
Definition ApexTypes.h:198
USTRUCT(BlueprintType)
Definition ApexTypes.h:393
FString DeviceId
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|Session Event")
Definition ApexTypes.h:419
USTRUCT(BlueprintType)
Definition ApexTypes.h:836
virtual void FromJsonObject(const TSharedPtr< FJsonObject > &JObject) override
Definition ApexTypes.h:867
virtual void FromJsonObject(const TSharedPtr< FJsonObject > &JObject) override
Definition ApexTypes.h:582
int ID
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|User Session Data")
Definition ApexTypes.h:525
FString FirstName
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|User Session Data")
Definition ApexTypes.h:543
int OrgUnit
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|User Session Data")
Definition ApexTypes.h:537
FString Email
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|User Session Data")
Definition ApexTypes.h:555
FString LastName
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|User Session Data")
Definition ApexTypes.h:549
FString SessionToken
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|User Session Data")
Definition ApexTypes.h:561
USTRUCT(BlueprintType)
Definition XAPITypes.h:564
FString ID
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|XAPI|Activity")
Definition XAPITypes.h:572
virtual bool IsEmpty() const override
Definition XAPITypes.h:593
USTRUCT(BlueprintType)
Definition XAPITypes.h:379
virtual bool IsEmpty() const override
Definition XAPITypes.h:420
FString MBox
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|XAPI|Agent")
Definition XAPITypes.h:394
FString Name
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|XAPI|Agent")
Definition XAPITypes.h:387
USTRUCT(BlueprintType)
Definition XAPITypes.h:648
FString Revision
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|XAPI|Context")
Definition XAPITypes.h:684
FGuid Registration
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|XAPI|Context")
Definition XAPITypes.h:656
FXAPIExtension Extensions
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|XAPI|Context")
Definition XAPITypes.h:698
FString Platform
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|XAPI|Context")
Definition XAPITypes.h:691
USTRUCT(BlueprintType)
void Clone(const FXAPIExtension &InExtension)
TSharedPtr< FJsonValue > & AddString(FString InKey, const FString &InValue)
TSharedPtr< FJsonValue > & AddCustomString(FString InKey, const FString &InValue)
virtual bool IsEmpty() const override
FString Add(FString InLanguage, FString InValue)
Definition XAPITypes.h:56
USTRUCT(BlueprintType)
Definition XAPITypes.h:899
TOptional< FTimespan > Duration
Definition XAPITypes.h:913
FXAPIScore Score
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|XAPI|Result")
Definition XAPITypes.h:920
TOptional< bool > Success
Definition XAPITypes.h:904
TOptional< bool > Completion
Definition XAPITypes.h:902
FXAPIExtension Extensions
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|XAPI|Result")
Definition XAPITypes.h:927
FOptionalFloat Max
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|XAPI|Score")
Definition XAPITypes.h:832
FOptionalFloat Min
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|XAPI|Score")
Definition XAPITypes.h:825
FOptionalFloat Scaled
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|XAPI|Score")
Definition XAPITypes.h:811
FOptionalFloat Raw
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|XAPI|Score")
Definition XAPITypes.h:818
FXAPIAgent Actor
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|XAPI|Statement")
Definition XAPITypes.h:1031
FXAPIResult Result
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|XAPI|Statement")
Definition XAPITypes.h:1052
FXAPIContext Context
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|XAPI|Statement")
Definition XAPITypes.h:1059
FXAPIVerb Verb
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|XAPI|Statement")
Definition XAPITypes.h:1038
FXAPIActivity Target
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|XAPI|Statement")
Definition XAPITypes.h:1045
USTRUCT(BlueprintType)
Definition XAPITypes.h:1156
virtual bool IsEmpty() const override
Definition XAPITypes.h:1188
USTRUCT(BlueprintType)
Definition XAPITypes.h:502
FString ID
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|XAPI|Verb")
Definition XAPITypes.h:510
FXAPILanguageMap Display
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Apex|XAPI|Verb")
Definition XAPITypes.h:517
virtual bool IsEmpty() const override
Definition XAPITypes.h:519