Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding back app insights #3093

Merged
merged 5 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/deploy-prod-static.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,10 @@ jobs:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4

# Update the site stats
- run: node packages/typescriptlang-org/scripts/updateAppInsightsGitHubIssue.js
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APP_INSIGHTS_API_KEY: ${{ secrets.APP_INSIGHTS_API_KEY }}
APP_INSIGHTS_ID: ${{ secrets.APP_INSIGHTS_ID }}
jakebailey marked this conversation as resolved.
Show resolved Hide resolved
5 changes: 5 additions & 0 deletions packages/playground/src/getExample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ export const getExampleSourceCode = async (prefix: string, lang: string, example
code = code.split("\n").slice(1).join("\n").trim()
}

// @ts-ignore
window.appInsights &&
// @ts-ignore
window.appInsights.trackEvent({ name: "Read Playground Example", properties: { id: exampleID, lang } })

return {
example,
code,
Expand Down
2 changes: 2 additions & 0 deletions packages/playground/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ export const setupPlayground = (
// When any compiler flags are changed, trigger a potential change to the URL
sandbox.setDidUpdateCompilerSettings(async () => {
playgroundDebouncedMainFunction()
// @ts-ignore
window.appInsights && window.appInsights.trackEvent({ name: "Compiler Settings changed" })

const model = sandbox.editor.getModel()
const plugin = getCurrentPlugin()
Expand Down
8 changes: 8 additions & 0 deletions packages/playground/src/sidebar/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ export const addCustomPlugin = (mod: string) => {
const newPlugins = customPlugins()
newPlugins.push(mod)
localStorage.setItem("custom-plugins-playground", JSON.stringify(newPlugins))
// @ts-ignore
window.appInsights &&
// @ts-ignore
window.appInsights.trackEvent({ name: "Added Custom Module", properties: { id: mod } })
}

const customPlugins = (): string[] => {
Expand Down Expand Up @@ -143,6 +147,10 @@ export const optionsPlugin: PluginFactory = (i, utils) => {
const ds = utils.createDesignSystem(div)
ds.declareRestartRequired(i)
if (input.checked) {
// @ts-ignore
window.appInsights &&
// @ts-ignore
window.appInsights.trackEvent({ name: "Added Registry Plugin", properties: { id: key } })
localStorage.setItem(key, "true")
} else {
localStorage.removeItem(key)
Expand Down
61 changes: 61 additions & 0 deletions packages/typescriptlang-org/gatsby-browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// This hooks ups client-side app analytics
// it's based on how the google analytics plugin works for gatsby
// https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-plugin-google-analytics/src/gatsby-browser.js

exports.onRouteUpdate = ({ location, prevLocation }) => {
// Run both clear and app insights for a bit, then drop app insights

// prettier-ignore
// ;(function(c,l,a,r,i,t,y){
// c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
// t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
// y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
// })(window, document, "clarity", "script", "3w5kyel345");

var sdkInstance = "appInsightsSDK"
window[sdkInstance] = "appInsights"
const config = {
instrumentationKey: "78a8fb52-a225-4c66-ac08-92fad1c1ade1",
// loggingLevelConsole: 1
}

let hasLocalStorage = false
try {
hasLocalStorage = typeof localStorage !== `undefined`
} catch (error) {}

try {
// prettier-ignore
// @ts-ignore
var aiName = window[sdkInstance], aisdk = window[aiName] || function (e) { function n(e) { t[e] = function () { var n = arguments; t.queue.push(function () { t[e].apply(t, n) }) } } var t = { config: e }; t.initialize = !0; var i = document, a = window; setTimeout(function () { var n = i.createElement("script"); n.async = true; n.src = e.url || "https://az416426.vo.msecnd.net/scripts/b/ai.2.min.js", i.getElementsByTagName("script")[0].parentNode.appendChild(n) }); try { t.cookie = i.cookie } catch (e) { } t.queue = [], t.version = 2; for (var r = ["Event", "PageView", "Exception", "Trace", "DependencyData", "Metric", "PageViewPerformance"]; r.length;)n("track" + r.pop()); n("startTrackPage"), n("stopTrackPage"); var s = "Track" + r[0]; if (n("start" + s), n("stop" + s), n("setAuthenticatedUserContext"), n("clearAuthenticatedUserContext"), n("flush"), !(!0 === e.disableExceptionTracking || e.extensionConfig && e.extensionConfig.ApplicationInsightsAnalytics && !0 === e.extensionConfig.ApplicationInsightsAnalytics.disableExceptionTracking)) { n("_" + (r = "onerror")); var o = a[r]; a[r] = function (e, n, i, a, s) { var c = o && o(e, n, i, a, s); return !0 !== c && t["_" + r]({ message: e, url: n, lineNumber: i, columnNumber: a, error: s }), c }, e.autoExceptionInstrumented = !0 } return t }(config);
window[aiName] = aisdk

const locationWithoutPlaygroundCode = location.pathname
.split("#code")[0]
.split("#src")[0]

const prevHref = (prevLocation && prevLocation.pathname) || ""
const previousLocationWithoutPlaygroundCode = prevHref
.split("#code")[0]
.split("#src")[0]

const referrerWithoutPlaygroundCode =
document.referrer && document.referrer.split("#code")[0].split("#src")[0]

// @ts-ignore
aisdk.trackPageView({
uri: locationWithoutPlaygroundCode,
refUri: referrerWithoutPlaygroundCode,
properties: {
uri: locationWithoutPlaygroundCode,
prev: previousLocationWithoutPlaygroundCode,
lang: document.documentElement.lang,
visitedPlayground:
hasLocalStorage && localStorage.getItem("sandbox-history") !== null,
},
})
} catch (error) {
console.error("Error with Application Insights")
console.error(error)
}
}
148 changes: 148 additions & 0 deletions packages/typescriptlang-org/scripts/makeMarkdownForAppInsights.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// @ts-check

/* Creates a markdown summary of the last week's worth of analytics,
* run with:
APP_INSIGHTS_ID="X" APP_INSIGHTS_API_KEY="Y" node packages/typescriptlang-org/scripts/makeMarkdownForAppInsights.js
*/

const nodeFetch = require("node-fetch").default
const querystring = require("querystring")

// Get these from: https://ms.portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/57bfeeed-c34a-4ffd-a06b-ccff27ac91b8/resourceGroups/typescriptlang-org/providers/microsoft.insights/components/TypeScriptLang-Prod-Ai/apiKeys
if (!process.env.APP_INSIGHTS_ID)
throw new Error("No App Insights ID at process.env.APP_INSIGHTS_ID")

if (!process.env.APP_INSIGHTS_API_KEY)
throw new Error("No App Insights ID at process.env.APP_INSIGHTS_API_KEY")

const getJSON = async (query, params) => {
const headers = {
"x-api-key": process.env.APP_INSIGHTS_API_KEY,
}

const queryParams = querystring.stringify(params)
const root = `https://api.applicationinsights.io/v1/apps/${process.env.APP_INSIGHTS_ID}`
const href = `${root}${query}?${queryParams}`
const response = await nodeFetch(href, { headers })

if (!response.ok) {
console.error("Error in API call to app insights")
console.error(response)
}

const json = await response.json()
return json
}

const makeQuery = query => getJSON("/query", { query })

const makeAToSitePath = path =>
`<a href='https://www.typescriptlang.org/${path}'>${path}</a>`

const makeAToPlaygroundSample = path =>
`<a href='https://www.staging-typescript.org/play/#example/${path}'>${path}</a>`

const makeAnchorAsNPMModule = path =>
`<a href='https://www.npmjs.com/package/${path}'>${path}</a>`

const toMDList = (rows, anchorFunc) => {
return rows
.sort((a, b) => b[1] - a[1])
.map(e => "- " + anchorFunc(e[0]) + ` (${e[1]})`)
.join("\n")
}

const makeMarkdownOfWeeklyAppInsightsInfo = async () => {
// You'll be looking at this stuff and think? Err how do I make these complex queries.
//
// It's actually pretty trivial, you do it all in the portal, then there is a button to get it:
// "Run last query in logs view" (it's like 9 dots, then a speech bubble above the bar graph)
// Which gives you the exact query for the data you see.

const likedPages = await makeQuery(
`let mainTable = union pageViews,customEvents | where timestamp > ago(1d) | where iif('*' in ("Liked Page"), 1==1, name in ("Liked Page")) | where customDimensions["slug"] startswith "/" ; let byTable = mainTable; let queryTable = () {byTable | extend dimension = customDimensions["slug"] | extend dimension = iif(isempty(dimension), "<undefined>", dimension)}; let byCohortTable = queryTable | project dimension, timestamp; let topSegments = byCohortTable | summarize Events = count() by dimension | top 10 by Events | summarize makelist(dimension); let topEventMetrics = byCohortTable | where dimension in (topSegments); let otherEventUsers = byCohortTable | where dimension !in (topSegments) | extend dimension = "Other"; otherEventUsers | union topEventMetrics | summarize Events = count() by dimension | order by dimension asc`
)

const dislikedPagesTable = await makeQuery(
`let mainTable = union pageViews,customEvents | where timestamp > ago(1d) | where iif('*' in ("Disliked Page"), 1==1, name in ("Disliked Page")) | where customDimensions["slug"] startswith "/" ; let byTable = mainTable; let queryTable = () {byTable | extend dimension = customDimensions["slug"] | extend dimension = iif(isempty(dimension), "<undefined>", dimension)}; let byCohortTable = queryTable | project dimension, timestamp; let topSegments = byCohortTable | summarize Events = count() by dimension | top 10 by Events | summarize makelist(dimension); let topEventMetrics = byCohortTable | where dimension in (topSegments); let otherEventUsers = byCohortTable | where dimension !in (topSegments) | extend dimension = "Other"; otherEventUsers | union topEventMetrics | summarize Events = count() by dimension | order by dimension asc`
)

const usedExamples = await makeQuery(
`let mainTable = union pageViews,customEvents | where timestamp > ago(7d) | where iif('*' in ("Read Playground Example"), 1==1, name in ("Read Playground Example")) | where true; let byTable = mainTable; let queryTable = () {byTable | extend dimension = customDimensions["id"] | extend dimension = iif(isempty(dimension), "<undefined>", dimension)}; let byCohortTable = queryTable | project dimension, timestamp; let topSegments = byCohortTable | summarize Events = count() by dimension | top 10 by Events | summarize makelist(dimension); let topEventMetrics = byCohortTable | where dimension in (topSegments); let otherEventUsers = byCohortTable | where dimension !in (topSegments) | extend dimension = "Other"; otherEventUsers | union topEventMetrics | summarize Events = count() by dimension | order by dimension asc`
)

const playgroundPluginsTable = await makeQuery(`let mainTable = union customEvents
| where timestamp > ago(7d)
| where iif('*' in ("Added Registry Plugin"), 1==1, name in ("Added Registry Plugin"))
| where true;
let byTable = mainTable;
let queryTable = ()
{
byTable
| extend dimension = customDimensions["id"]
| extend dimension = iif(isempty(dimension), "<undefined>", dimension)
};
let byCohortTable = queryTable
| project dimension, timestamp;
let topSegments = byCohortTable
| summarize Events = count() by dimension
| top 10 by Events
| summarize makelist(dimension);
let topEventMetrics = byCohortTable
| where dimension in (topSegments);
let otherEventUsers = byCohortTable
| where dimension !in (topSegments)
| extend dimension = "Other";
otherEventUsers
| union topEventMetrics
| summarize Events = count() by dimension
| order by dimension asc`)

const mds = []

mds.push(
`Hello! This is an always updating GitHub Issue which pulls out the last week of interesting eco-system analytics from the TypeScript website and makes it available for everyone. If you have ideas for things you'd like to see in here, feel free to comment. Microsoft staff can find the [PM focused version here](https://dev.azure.com/devdiv/DevDiv/_dashboards/dashboard/bf4dee3f-7c4b-42b0-805b-670de64052e5).`
)

const mostLikedPages = likedPages.tables[0].rows
mds.push(`#### Most liked/disliked pages`)
mds.push(
"All documentation pages have :+1: and :-1: next to them asking _'Is this page helpful?'_. This is the aggregate of the last week for the results."
)
mds.push(`###### Most Helpful`)
mds.push(toMDList(mostLikedPages, makeAToSitePath))

const mostdisLikedPages = dislikedPagesTable.tables[0].rows
mds.push(`###### Least Helpful`)
mds.push(toMDList(mostdisLikedPages, makeAToSitePath))

const examples = usedExamples.tables[0].rows.filter(a => a[0] !== "Other")

mds.push(`#### Playground Examples`)
mds.push("What code samples in the playground are getting used?")
mds.push(toMDList(examples, makeAToPlaygroundSample))

const plugins = playgroundPluginsTable.tables[0].rows
.sort((a, b) => b[1] - a[1])
.filter(a => a[0] !== "Other")

mds.push(`#### Playground Plugins`)
mds.push(
"What Playground Plugins are being used? This only counts folks clicking in the registry in the sidebar"
)
mds.push(toMDList(plugins, makeAnchorAsNPMModule))

const today = new Date()
mds.push(
`This was last updated ${today.getDate()}/${today.getMonth()}/${today.getFullYear()}. Created with [this script](https://github.com/microsoft/TypeScript-website/blob/v2/packages/typescriptlang-org/scripts/makeMarkdownForAppInsights.js).`
)

return mds.join("\n\n")
}

// @ts-ignore
if (!module.parent) {
makeMarkdownOfWeeklyAppInsightsInfo().then(console.log)
}

module.exports = { makeMarkdownOfWeeklyAppInsightsInfo }
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// @ts-check

// Uses the App Insights API to grab useful analytics

// See: https://dev.applicationinsights.io/reference/get-events
// https://ms.portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/57bfeeed-c34a-4ffd-a06b-ccff27ac91b8/resourceGroups/typescriptlang-org/providers/microsoft.insights/components/TypeScriptLang-Prod-Ai/events
//

const {
makeMarkdownOfWeeklyAppInsightsInfo,
} = require("./makeMarkdownForAppInsights")
const Octokit = require("@octokit/rest")

// Get this from OneNote
if (!process.env.GITHUB_TOKEN)
throw new Error("No GitHub Token at process.env.GITHUB_TOKEN")

const go = async () => {
const octokit = new Octokit({
auth: process.env.GITHUB_TOKEN,
userAgent: "TS AppInsights Issue Updater",
})

const md = await makeMarkdownOfWeeklyAppInsightsInfo()

await octokit.issues.update({
owner: "Microsoft",
repo: "TypeScript-Website",
issue_number: 1014,
body: md,
})
}

go()
12 changes: 10 additions & 2 deletions packages/typescriptlang-org/src/components/index/AboveTheFold.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ const Row = (props: { children: any, className?: string }) => <div className={[p
const Col = (props: { children: any, className?: string }) => <div className={[props.className, "col1"].join(" ")}>{props.children}</div>
const Col2 = (props: { children: any }) => <div className="col2">{props.children}</div>

const event = (name: string, options?: any) => {
// @ts-ignore
window.appInsights &&
// @ts-ignore
window.appInsights.trackEvent({ name }, options)
}

const FluidButton = (props: { href?: string, onClick?: any, title: string, subtitle?: string, icon: JSX.Element, className?: string }) => (
<a className={"fluid-button " + props.className || ""} href={props.href} onClick={props.onClick}>
<div>
Expand All @@ -29,6 +36,7 @@ export const AboveTheFold = () => {
const onclick = (e) => {
setShowCTALinks(true)
e.preventDefault()
event("Home Page CTA Started")
return false
}

Expand Down Expand Up @@ -68,7 +76,7 @@ export const AboveTheFold = () => {
title={i("index_2_cta_play")}
subtitle={i("index_2_cta_play_subtitle")}
href="/play"
onClick={() => ({ link: "playground" })}
onClick={() => event("Home Page CTA Exited", { link: "playground" })}
icon={
<svg width="33" height="33" viewBox="0 0 33 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0)">
Expand All @@ -92,7 +100,7 @@ export const AboveTheFold = () => {
title={i("index_2_cta_download")}
subtitle={i("index_2_cta_download_subtitle")}
href="/download"
onClick={() => ({ link: "download" })}
onClick={() => event("Home Page CTA Exited", { link: "download" })}
icon={
<svg width="15" height="27" viewBox="0 0 15 27" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.5 0.5V19M7.5 19L1 13M7.5 19L13 13" stroke="black" strokeWidth="1.5" />
Expand Down
14 changes: 7 additions & 7 deletions packages/typescriptlang-org/src/templates/documentation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import { createIntlLink } from "../components/IntlLink"
import { handbookCopy } from "../copy/en/handbook"
import { Contributors } from "../components/handbook/Contributors"
import { overrideSubNavLinksWithSmoothScroll, updateSidebarOnScroll } from "./scripts/setupSubNavigationSidebar"
// import { setupLikeDislikeButtons } from "./scripts/setupLikeDislikeButtons"
// import { DislikeUnfilledSVG, LikeUnfilledSVG } from "../components/svgs/documentation"
import { setupLikeDislikeButtons } from "./scripts/setupLikeDislikeButtons"
import { DislikeUnfilledSVG, LikeUnfilledSVG } from "../components/svgs/documentation"
import { Popup, useQuickInfoPopup } from "../components/Popup"
import Helmet from "react-helmet"

Expand Down Expand Up @@ -69,7 +69,7 @@ const HandbookTemplate: React.FC<Props> = (props) => {
// Sets current selection
updateSidebarOnScroll()

// setupLikeDislikeButtons(props.pageContext.slug, i)
setupLikeDislikeButtons(props.pageContext.slug, i)


return () => {
Expand All @@ -95,13 +95,13 @@ const HandbookTemplate: React.FC<Props> = (props) => {
<section id="doc-layout" >
<SidebarToggleButton />

{/* <div className="page-popup" id="page-helpful-popup" style={{ opacity: 0, display: "none" }}>
<div className="page-popup" id="page-helpful-popup" style={{ opacity: 0, display: "none" }}>
<p>Was this page helpful?</p>
<div>
<button className="first" id="like-button-popup" title="Like this page"><LikeUnfilledSVG /></button>
<button id="dislike-button-popup" title="Dislike this page"><DislikeUnfilledSVG /></button>
</div>
</div> */}
</div>

<noscript>
{/* Open by default so that folks without JS get a fully open sidebar */}
Expand Down Expand Up @@ -165,13 +165,13 @@ const HandbookTemplate: React.FC<Props> = (props) => {
<MarkdownHeadingTree tree={headerListToTree(sidebarHeaders)} className="handbook-on-this-page-section-list" slug={slug} />
</>
}
{/* <div id="like-dislike-subnav">
<div id="like-dislike-subnav">
<h5>{i("handb_like_dislike_title")}</h5>
<div>
<button title="Like this page" id="like-button"><LikeUnfilledSVG /> {i("handb_like_desc")}</button>
<button title="Dislike this page" id="dislike-button"><DislikeUnfilledSVG /> {i("handb_dislike_desc")}</button>
</div>
</div> */}
</div>

</nav>
</aside>
Expand Down
Loading