Skip to content

Commit 7f1a94d

Browse files
srietkerkriknoll
andauthored
OCV first pass (#10355)
* plumbing for showing the feedback iframe * add https flag to the cli to use https when debugging ocv iframe * more config, removed functions, changed height and width of iframe * using different modal for the feedback * added feedback menu item to editor menu * got rid of iframe border * some small changes and styling * added comments * move app id to configs * fix onclose reference * moved frame url to configs * remove tutorial completion feedback for now * use emoji instead of sui icon * update iframeelement variable in event listener Co-authored-by: Richard Knoll <riknoll@users.noreply.github.com> * add space to less file Co-authored-by: Richard Knoll <riknoll@users.noreply.github.com> * another iframe element var change Co-authored-by: Richard Knoll <riknoll@users.noreply.github.com> * moved height, width to less file, fixed some styling * remove redundant jsdoc types * use pxt log functions, fix spacing in the event listener * move emoji out of lf * use lf for the displayed strings for the rating * types added * move types to localtypings and wrap them in a namespace * change to using the comment bubble icon * added types * actually using feedback kind * rename feedback app theme field * move iframe init and posting code to helper function * use optional chaining for ondismiss call * move current theme into send update theme function, update theme options * move questions for rating config to a function * move appid, frame url to apptheme --------- Co-authored-by: Richard Knoll <riknoll@users.noreply.github.com>
1 parent 41f8784 commit 7f1a94d

File tree

13 files changed

+647
-9
lines changed

13 files changed

+647
-9
lines changed

cli/cli.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2805,7 +2805,6 @@ function renderDocs(builtPackaged: string, localDir: string) {
28052805
}
28062806
pxt.log(`All docs written.`);
28072807
}
2808-
28092808
export function serveAsync(parsed: commandParser.ParsedCommand) {
28102809
// always use a cloud build
28112810
// in most cases, the user machine is not properly setup to
@@ -2864,6 +2863,7 @@ export function serveAsync(parsed: commandParser.ParsedCommand) {
28642863
serial: !parsed.flags["noSerial"] && !globalConfig.noSerial,
28652864
noauth: parsed.flags["noauth"] as boolean || false,
28662865
backport: parsed.flags["backport"] as number || 0,
2866+
https: parsed.flags["https"] as boolean || false,
28672867
}))
28682868
}
28692869

@@ -7129,7 +7129,8 @@ ${pxt.crowdin.KEY_VARIABLE} - crowdin key
71297129
description: "port where the locally running backend is listening.",
71307130
argument: "backport",
71317131
type: "number",
7132-
}
7132+
},
7133+
https: { description: "use https protocol instead of http"}
71337134
}
71347135
}, serveAsync);
71357136

cli/server.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as fs from 'fs';
22
import * as path from 'path';
33
import * as http from 'http';
4+
import * as https from 'https';
45
import * as url from 'url';
56
import * as querystring from 'querystring';
67
import * as nodeutil from './nodeutil';
@@ -13,6 +14,7 @@ import { promisify } from "util";
1314

1415
import U = pxt.Util;
1516
import Cloud = pxt.Cloud;
17+
import { SecureContextOptions } from 'tls';
1618

1719
const userProjectsDirName = "projects";
1820

@@ -804,6 +806,7 @@ export interface ServeOptions {
804806
serial?: boolean;
805807
noauth?: boolean;
806808
backport?: number;
809+
https?: boolean;
807810
}
808811

809812
// can use http://localhost:3232/streams/nnngzlzxslfu for testing
@@ -949,8 +952,7 @@ export function serveAsync(options: ServeOptions) {
949952
const wsServerPromise = initSocketServer(serveOptions.wsPort, serveOptions.hostname);
950953
if (serveOptions.serial)
951954
initSerialMonitor();
952-
953-
const server = http.createServer(async (req, res) => {
955+
const reqListener: http.RequestListener = async (req, res) => {
954956
const error = (code: number, msg: string = null) => {
955957
res.writeHead(code, { "Content-Type": "text/plain" })
956958
res.end(msg || "Error " + code)
@@ -1351,7 +1353,10 @@ export function serveAsync(options: ServeOptions) {
13511353
});
13521354
}
13531355
return
1354-
});
1356+
};
1357+
const canUseHttps = serveOptions.https && process.env["HTTPS_KEY"] && process.env["HTTPS_CERT"];
1358+
const httpsServerOptions: SecureContextOptions = {cert: process.env["HTTPS_CERT"], key: process.env["HTTPS_KEY"]};
1359+
const server = canUseHttps ? https.createServer(httpsServerOptions, reqListener) : http.createServer(reqListener);
13551360

13561361
// if user has a server.js file, require it
13571362
const serverjs = path.resolve(path.join(root, 'built', 'server.js'))
@@ -1362,12 +1367,16 @@ export function serveAsync(options: ServeOptions) {
13621367

13631368
const serverPromise = new Promise<void>((resolve, reject) => {
13641369
server.on("error", reject);
1365-
server.listen(serveOptions.port, serveOptions.hostname, () => resolve());
1370+
server.listen(serveOptions.port, serveOptions.hostname, () => {
1371+
console.log(`Server listening on port ${serveOptions.port}`);
1372+
return resolve()
1373+
});
13661374
});
13671375

13681376
return Promise.all([wsServerPromise, serverPromise])
13691377
.then(() => {
1370-
const start = `http://${serveOptions.hostname}:${serveOptions.port}/#local_token=${options.localToken}&wsport=${serveOptions.wsPort}`;
1378+
const protocol = canUseHttps ? "https" : "http";
1379+
const start = `${protocol}://${serveOptions.hostname}:${serveOptions.port}/#local_token=${options.localToken}&wsport=${serveOptions.wsPort}`;
13711380
console.log(`---------------------------------------------`);
13721381
console.log(``);
13731382
console.log(`To launch the editor, open this URL:`);

localtypings/ocv.d.ts

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
declare namespace ocv {
2+
3+
type FeedbackKind = "generic" | "rating";
4+
5+
const enum FeedbackAgeGroup {
6+
Undefined = "Undefined",
7+
MinorWithoutParentalConsent = "MinorWithoutParentalConsent",
8+
MinorWithParentalConsent = "MinorWithParentalConsent",
9+
NotAdult = "NotAdult",
10+
Adult = "Adult",
11+
MinorNoParentalConsentRequired = "MinorNoParentalConsentRequired"
12+
}
13+
14+
interface IFeedbackCallbackFunctions {
15+
attachDiagnosticsLogs?: (diagnosticsUploadId: string, diagnosticsEndpoint: string) => void;
16+
onDismiss?: (isFeedbackSent?: boolean) => void;
17+
onSuccess?: (clientFeedbackId: string) => void;
18+
onError?: (errorMessage?: string) => void;
19+
supportCallback?: () => void;
20+
initializationComplete?: (initializationCompleteResult: InitializationResult) => void;
21+
setSubmitButtonState?: (isEnabled: boolean) => void;
22+
}
23+
24+
const enum FeedbackAuthenticationType {
25+
MSA = "MSA",
26+
AAD = "AAD",
27+
Unauthenticated = "Unauthenticated"
28+
}
29+
30+
const enum FeedbackType {
31+
Smile = "Smile",
32+
Frown = "Frown",
33+
Idea = "Idea",
34+
Unclassified = "Unclassified",
35+
Survey = "Survey"
36+
}
37+
38+
const enum FeedbackPolicyValue {
39+
Enabled = "Enabled",
40+
Disabled = "Disabled",
41+
NotConfigured = "Not Configured",
42+
NotApplicable = "Not Applicable"
43+
}
44+
45+
interface IThemeOptions {
46+
isFluentV9?: boolean;
47+
/**
48+
* v9Theme must be Theme object from @fluentui/react-components@9.*
49+
*/
50+
v9Theme?: any;
51+
/**
52+
* brandVariants must be BrandVariants object from @fluentui/react-components@9.*
53+
*/
54+
brandVariants?: any;
55+
baseTheme?: any;
56+
colorScheme?: any;
57+
}
58+
59+
interface IFeedbackInitOptions {
60+
ageGroup?: FeedbackAgeGroup;
61+
appId?: number;
62+
authenticationType?: FeedbackAuthenticationType;
63+
callbackFunctions?: IFeedbackCallbackFunctions;
64+
clientName?: string;
65+
feedbackConfig?: IFeedbackConfig;
66+
isProduction?: boolean;
67+
telemetry?: IFeedbackTelemetry;
68+
themeOptions?: IThemeOptions;
69+
}
70+
71+
const enum FeedbackUiType {
72+
SidePane = "SidePane",// Default: Used for side pane/detail view
73+
Modal = "Modal",// Used for modal view
74+
CallOut = "CallOut",// Used for inscreen pop up dialogue
75+
IFrameWithinSidePane = "IFrameWithinSidePane",// Same as side pane but used inside an iframe
76+
IFrameWithinModal = "IFrameWithinModal",// Same as modal but used inside an iframe
77+
IFrameWithinCallOut = "IFrameWithinCallOut",// Same as callout but used inside an iframe
78+
NoSurface = "NoSurface",// Used when the surface is provided by the host app
79+
NoSurfaceWithoutTitle = "NoSurfaceWithoutTitle"
80+
}
81+
82+
const enum FeedbackHostPlatformType {
83+
Windows = "Windows",
84+
IOS = "iOS",
85+
Android = "Android",
86+
WacTaskPane = "WacTaskPane",
87+
MacOS = "MacOS",
88+
Web = "Web",
89+
IFrame = "IFrame"
90+
}
91+
92+
const enum FeedbackHostEventName {
93+
SubmitClicked = "InAppFeedback_HostEvent_SubmitClicked",
94+
BackClicked = "InAppFeedback_HostEvent_BackClicked"
95+
}
96+
97+
const enum InitializationStatus {
98+
Success = "Success",
99+
Error = "Error",
100+
Warning = "Warning"
101+
}
102+
103+
const enum InAppFeedbackQuestionUiType {
104+
DropDown = "DropDown",
105+
MultiSelect = "MultiSelect",
106+
Rating = "Rating",
107+
SingleSelect = "SingleSelect",
108+
SingleSelectHorizontal = "SingleSelectHorizontal"
109+
}
110+
111+
const enum InAppFeedbackScenarioType {
112+
FeatureArea = "FeatureArea",
113+
ResponsibleAI = "ResponsibleAI",
114+
Experience = "Experience",
115+
ProductSatisfaction = "ProductSatisfaction",
116+
CrashImpact = "CrashImpact",// CrashImpact is of type Survey
117+
Custom = "Custom",
118+
AIThumbsDown = "AIThumbsDown",
119+
AIThumbsUp = "AIThumbsUp",
120+
AIError = "AIError",
121+
PromptSuggestion = "PromptSuggestion"
122+
}
123+
124+
const enum InAppFeedbackQuestionUiBehaviour {
125+
QuestionNotRequired = "QuestionNotRequired",
126+
CommentNotRequired = "CommentNotRequired",
127+
CommentRequiredWithLastOption = "CommentRequiredWithLastOption"
128+
}
129+
130+
const enum FeedbackAttachmentOrigin {
131+
Application = "Application",
132+
User = "User"
133+
}
134+
135+
const enum FeedbackEntryPoint {
136+
Header = "Header",
137+
Footer = "Footer",
138+
Backstage = "Backstage",
139+
HelpMenu = "Help Menu",
140+
Canvas = "Canvas",
141+
Chat = "Chat"
142+
}
143+
144+
interface InitializationResult {
145+
status: InitializationStatus;
146+
/**
147+
* in UTC timestamp milliseconds
148+
*/
149+
timestamp?: number;
150+
/**
151+
* Duration to load package and validations (centro performance) in milliseconds
152+
*/
153+
loadTime?: number;
154+
errorMessages?: string[];
155+
warningMessages?: string[];
156+
}
157+
158+
interface IFeedbackConfig {
159+
appData?: string;
160+
canDisplayFeedbackCalled?: boolean;
161+
feedbackUiType?: FeedbackUiType;
162+
hideFooterActionButtons?: boolean;
163+
initialFeedbackType?: FeedbackType;
164+
hostPlatform?: FeedbackHostPlatformType;
165+
/**
166+
* Invokes onDismiss callback on Esc button press
167+
* Useful for host apps like Win32 Pane or iFrames
168+
*/
169+
invokeOnDismissOnEsc?: boolean;
170+
isDisplayed?: boolean;
171+
isEmailCollectionEnabled?: boolean;
172+
isFeedbackForumEnabled?: boolean;
173+
isFileUploadEnabled?: boolean;
174+
isMyFeedbackEnabled?: boolean;
175+
isScreenRecordingEnabled?: boolean;
176+
isScreenshotEnabled?: boolean;
177+
isShareContextDataEnabled?: boolean;
178+
isThankYouPageDisabled?: boolean;
179+
isSupportEnabled?: boolean;
180+
maxHeight?: number;
181+
maxWidth?: number;
182+
minHeight?: number;
183+
minWidth?: number;
184+
myFeedbackUrl?: string;
185+
privacyUrl?: string;
186+
retentionDurationDays?: number;
187+
scenarioConfig?: InAppFeedbackScenarioConfig;
188+
supportUrl?: string;
189+
/**
190+
* Enable submit offline feedback
191+
* This will only work if submitOffline callback is provided
192+
*/
193+
isOfflineSubmitEnabled?: boolean;
194+
/**
195+
* For platforms that host other products or sites, this parameter is used to disambiguate the recipient of the data.
196+
* Its effect is to alter the form title for internal users only, replacing 'Microsoft' with the string provided.
197+
* The string length is capped at 30 characters.
198+
* Please keep the name as short as possible to optimize the user experience, preferably including only the product name.
199+
* */
200+
msInternalTitleTarget?: string;
201+
}
202+
203+
type IFeedbackTelemetry = {
204+
accountCountryCode?: string;
205+
affectedProcessSessionId?: string;
206+
appVersion?: string;
207+
audience?: string;
208+
audienceGroup?: string;
209+
browser?: string;
210+
browserVersion?: string;
211+
channel?: string;
212+
clientCountryCode?: string;
213+
cpuModel?: string;
214+
dataCenter?: string;
215+
deviceId?: string;
216+
deviceType?: string;
217+
entryPoint?: FeedbackEntryPoint;
218+
errorClassification?: string;
219+
errorCode?: string;
220+
errorName?: string;
221+
featureArea?: string;
222+
featureName?: string;
223+
feedbackOrigin?: string;
224+
flights?: string;
225+
flightSource?: string;
226+
fundamentalArea?: string;
227+
installationType?: string;
228+
isLogIncluded?: boolean;
229+
isUserSubscriber?: boolean;
230+
officeArchitecture?: string;
231+
officeBuild?: string;
232+
officeEditingLang?: number;
233+
officeUILang?: number;
234+
osBitness?: number;
235+
osBuild?: string;
236+
osUserLang?: number;
237+
platform?: string;
238+
processorArchitecture?: string;
239+
processorPhysicalCores?: number;
240+
processSessionId?: string;
241+
ringId?: number;
242+
sku?: string;
243+
sourceContext?: string;
244+
sqmMachineId?: string;
245+
subFeatureName?: string;
246+
sourcePageName?: string;
247+
sourcePageURI?: string;
248+
systemManufacturer?: string;
249+
systemProductName?: string;
250+
uiHost?: string;
251+
};
252+
253+
interface InAppFeedbackScenarioConfig {
254+
isScenarioEnabled?: boolean;
255+
scenarioType?: InAppFeedbackScenarioType;
256+
questionDetails?: InAppFeedbackQuestion;
257+
}
258+
259+
interface InAppFeedbackQuestion {
260+
questionUiType?: InAppFeedbackQuestionUiType;
261+
questionInstruction?: InAppFeedbackCompositeString;
262+
questionOptions?: InAppFeedbackCompositeString[];
263+
questionUiBehaviour?: InAppFeedbackQuestionUiBehaviour[];
264+
}
265+
266+
interface InAppFeedbackCompositeString {
267+
displayedString: string;
268+
displayedStringInEnglish: string;
269+
}
270+
}
271+
272+
273+
274+

localtypings/pxtarget.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,9 @@ declare namespace pxt {
525525
timeMachineDiffInterval?: number; // An interval in milliseconds at which to take diffs to store in project history. Defaults to 5 minutes
526526
timeMachineSnapshotInterval?: number; // An interval in milliseconds at which to take full project snapshots in project history. Defaults to 15 minutes
527527
adjustBlockContrast?: boolean; // If set to true, all block colors will automatically be adjusted to have a contrast ratio of 4.5 with text
528+
feedbackEnabled?: boolean; // allow feedback to be shown on a target
529+
ocvAppId?: number; // the app id needed to attach to the OCV service
530+
ocvFrameUrl?: string; // the base url for the OCV service
528531
}
529532

530533
interface DownloadDialogTheme {

0 commit comments

Comments
 (0)