// general helper functions String.prototype.capitalize = function () { return this.charAt(0).toUpperCase() + this.slice(1); }; Array.prototype.average = function() { let sum = this.reduce(function(a, b) {return parseInt(a) + parseInt(b)}); return sum / this.length; }; // translate a decimal value on a scale of 1-5 to an integer on a scale of 0-100, rounded to the nearest 10. function fiveScaleToDecile(input) { return Math.round(((input-1) * 25) / 10) * 10; } // analysis helper functions // algorithm from Handbook of Mathematical Functions (Abramowitz & Stegun), // formula 7.1.26 function normDist(x, mean, standardDeviation) { const a1 = 0.254829592; const a2 = -0.284496736; const a3 = 1.421413741; const a4 = -1.453152027; const a5 = 1.061405429; const p = 0.3275911; let val = (mean - x) / (Math.sqrt(2) * standardDeviation); let sign = (val < 0) ? -1 : 1; var xabs = Math.abs(val); var t = 1.0 / (1.0 + xabs * p); var y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-xabs * xabs); var erf = sign * y; return (1 - erf) / 2; } function clamp(value, max) { return Math.max(Math.min(value, max), 1); } function scalePerf(value) { return 20 * (value - 1); } const indicators = { 'leadtime': { 'label': 'Lead time', 'ticks': [ { v: 0, f: '>6m' }, { v: 20, f: '1-6m' }, { v: 40, f: '1w - 1m' }, { v: 60, f: '1d - 1w' }, { v: 80, f: '<1d' }, { v: 100, f: '<1h' } ] }, 'deployfreq': { 'label': 'Deploy frequency', 'ticks': [ { v: 0, f: '>6m' }, { v: 20, f: '1-6m' }, { v: 40, f: '1w - 1m' }, { v: 60, f: '1d - 1w' }, { v: 80, f: '<1d' }, { v: 100, f: 'on demand' } ] }, 'ttr': { 'label': 'Time to restore', 'ticks': [ { v: 0, f: '>6m' }, { v: 20, f: '1-6m' }, { v: 40, f: '1w - 1m' }, { v: 60, f: '1d - 1w' }, { v: 80, f: '<1d' }, { v: 100, f: '<1h' } ] }, 'chgfail': { 'label': 'Change fail rate', 'ticks': [ { v: 0, f: '76-100%' }, { v: 20, f: '61-75%' }, { v: 40, f: '46-60%' }, { v: 60, f: '31-45%' }, { v: 80, f: '16-30%' }, { v: 100, f: '0-15%' } ] } }; ; // data constants for 2019 const mean = 4.251338; const stddev = 1.000992; const profileStats = { 'low': 3.5, 'medium': 4.4, 'high': 5.2, 'elite': 6 }; const colors = { 'low': '#D93025', 'medium': '#FBBC04', 'high': '#34A853', 'elite': '#0D652D', 'you': '#0F346F', 'average': '#afb2b6', 'bar': '#0F346F' } const baselines = { 'all': { 'leadtime': 50, 'deployfreq': 49, 'ttr': 78, 'chgfail': 88 }, 'education': { 'leadtime': 52, 'deployfreq': 49, 'ttr': 73, 'chgfail': 96 }, 'energy': { 'leadtime': 40, 'deployfreq': 35, 'ttr': 73, 'chgfail': 81 }, 'finance': { 'leadtime': 46, 'deployfreq': 44, 'ttr': 78, 'chgfail': 84 }, 'government': { 'leadtime': 38, 'deployfreq': 36, 'ttr': 70, 'chgfail': 88 }, 'healthcare': { 'leadtime': 41, 'deployfreq': 42, 'ttr': 68, 'chgfail': 82 }, 'industrials': { 'leadtime': 40, 'deployfreq': 39, 'ttr': 79, 'chgfail': 91 }, 'insurance': { 'leadtime': 49, 'deployfreq': 51, 'ttr': 80, 'chgfail': 91 }, 'media': { 'leadtime': 58, 'deployfreq': 65, 'ttr': 85, 'chgfail': 85 }, 'nonprofit': { 'leadtime': 67, 'deployfreq': 73, 'ttr': 93, 'chgfail': 98 }, 'retail': { 'leadtime': 59, 'deployfreq': 67, 'ttr': 87, 'chgfail': 89 }, 'technology': { 'leadtime': 51, 'deployfreq': 51, 'ttr': 78, 'chgfail': 90 }, 'telecoms': { 'leadtime': 44, 'deployfreq': 43, 'ttr': 79, 'chgfail': 81 }, 'other': { 'leadtime': 51, 'deployfreq': 48, 'ttr': 80, 'chgfail': 89 } } const allCapabilities = { "elite": { "westrum": { "title": "Westrum organizational culture", "description": "Westrum’s measure of organizational culture is predictive of IT performance, organizational performance, and decreasing burnout. Hallmarks of this measure include good information flow, high cooperation and trust, bridging between teams, and conscious inquiry.", "mean": 77, "profile-mean": 85, "url": "/devops-capabilities/cultural/generative-organizational-culture/", "img": "https://www.google.com/images/icons/material/system/svg/support_24px.svg", "context": "How strongly do you agree or disagree with the following? On my team:", "questions": [ "Information is actively sought.", "Messengers are not punished when they deliver news of failures or other bad news.", "Responsibilities are shared.", "Cross-functional collaboration is encouraged and rewarded.", "New ideas are welcomed.", "Failures are treated primarily as opportunities to improve the system." ] }, "monitoring": { "title": "Monitoring and observability", "description": "A comprehensive monitoring and observability system allows teams to understand the health of their systems. Effective solutions enable monitoring predefined metrics, including system state as experienced by users as well as allowing engineers to interactively debug systems and explore properties and patterns as they emerge.", "mean": 67, "profile-mean": 75, "url": "/devops-capabilities/technical/monitoring-and-observability/", "img": "https://www.google.com/images/icons/material/system/svg/analytics_24px.svg", "context": "How do you watch and understand your systems at work? Please rate how strongly you agree or disagree with the following statements:", "questions": [ "My team has a tech solution in place to report on the overall health of systems (e.g, are my systems functioning? / do my systems have sufficient resources available?).", "My team has a tech solution in place to report on system state as experienced by customers (e.g., “do my customers know if my system is down and have a bad experience?”).", "My team has a tech solution in place for monitoring key business and systems metrics.", "My team has tooling in place that can help us with understanding and debugging our systems in production.", "My team has tooling in place that provides the ability to find information about things we did not previously know (e.g., we can identify “unknown unknowns”).", "My team has access to tools and data which help us trace, understand, and diagnose infrastructure problems in our production environment, including interactions between services." ] }, "mvp": { "title": "Working in small batches", "description": "Teams should slice work into small pieces that can be completed in a week or less. The key is to have work decomposed into small features that allow for rapid development, instead of developing complex features on branches and releasing them infrequently. This idea can be applied at the feature and the product level. (An MVP is a prototype of a product with just enough features to enable validated learning about the product and its business model.) Working in small batches enables short lead times and faster feedback loops.", "mean": 61, "profile-mean": 83, "url": "/devops-capabilities/process/working-in-small-batches/", "img": "https://www.google.com/images/icons/material/system/svg/communities_24px.svg", "context": "The following questions ask about how work is sized. Please rate how strongly you agree or disagree with the following statements.", "questions": [ "Our features are sliced in a way that lend themselves to frequent production releases.", "Our features are decomposed in a way that allows a developer to complete the work in a week or less.", "Our work is decomposed into features that allow for minimum viable products (MVPs) and rapid development, rather than complex and lengthy processes (an MVP has just enough features to get validated learning about the product & its continued development)." ] } }, "high": { "learn": { "title": "Learning culture", "description": "Is learning, in your culture, considered essential for continued progress? Is learning thought of as a cost or an investment? This is a measure of an organization’s learning culture.", "mean": 82, "profile-mean": 85, "url": "/devops-capabilities/cultural/learning-culture/", "img": "https://www.google.com/images/icons/material/system/svg/school_24px.svg", "context": "Please rate how strongly you agree or disagree with the following statements as descriptive of your organization.", "questions": [ "Learning is the key to improvement.", "Once we quit learning we endanger our future.", "Learning is viewed as an investment, not an expense." ] }, "wip": { "title": "Work in process limits", "description": "The use of work-in-process limits to manage the flow of work is well known in the Lean community. When used effectively, this drives process improvement, increases throughput, and makes constraints visible in the system.", "mean": 54, "profile-mean": 56, "url": "/devops-capabilities/process/wip-limits/", "img": "https://www.google.com/images/icons/material/system/svg/compress_24px.svg", "context": "Please rate how strongly you agree or disagree with the following statements:", "questions": [ "As a team, we are good at limiting our work in process (WIP).", "We strive to limit our WIP, and have processes in place to do so.", "Our WIP limits make obstacles to higher flow visible.", "Our WIP limits lead to process improvement.", "WIP limits are used as a way to improve our throughput." ] }, "trunk": { "title": "Trunk-based development", "description": "Trunk-based development has been shown to be a predictor of high performance in software development and delivery. It is characterized by fewer than three active branches in a code repository; branches and forks having very short lifetimes (e.g., less than a day) before being merged into master; and application teams rarely or never having “code lock” periods when no one can check in code or do pull requests due to merging conflicts, code freezes, or stabilization phases.", "mean": 54, "profile-mean": 60, "url": "/devops-capabilities/technical/trunk-based-development/", "img": "https://www.google.com/images/icons/material/system/svg/integration_instructions_24px.svg", "context": "For the primary application or service you work on:", "questions": [ "All developers on my team push code to trunk/master at least daily.", "There are fewer than three active branches on the application's code repo.", "Our application team never has code lock periods when no one can check in code or do pull requests due to merging conflicts.", "Branches and forks have very short lifetimes (less than a day) before being merged to master." ] } }, "medium": { "ci": { "title": "Continuous integration", "description": "Continuous integration (CI) is the first step towards continuous delivery. This is a development practice where code is regularly checked in, and each check-in triggers a set of quick tests to discover serious regressions, which developers fix immediately. The CI process creates canonical builds and packages that are ultimately deployed and released.", "mean": 66, "profile-mean": 61, "url": "/devops-capabilities/technical/continuous-integration/", "img": "https://www.google.com/images/icons/material/system/svg/all_inclusive_24px.svg", "context": "For the primary application or service you work on:", "questions": [ "Code commits result in an automated build of the software.", "Code commits result in a series of automated tests being run.", "Automated builds and tests are executed successfully every day.", "Current builds are available to testers for exploratory testing.", "Developers get feedback from the acceptance and performance tests every day." ] }, "cd": { "title": "Continuous delivery", "description": "Continuous delivery (CD) is the ability to release changes of all kinds on demand quickly, safely, and sustainably. Teams that practice continuous delivery well are able to release software and make changes to production in a low-risk way at any time — including during normal business hours — without impacting users.", "mean": 65, "profile-mean": 61, "url": "/devops-capabilities/technical/continuous-delivery/", "img": "https://www.google.com/images/icons/material/system/svg/swap_driving_apps_wheel_24px.svg", "context": "For the primary application or service you work on:", "questions": [ "Our software is in a deployable state throughout its lifecycle.", "My team prioritizes keeping the software deployable over working on new features.", "Fast feedback on the quality and deployability of the system is available to anyone on the team.", "When people get feedback that the system is not deployable (such as failing builds or tests), they make fixing these issues their highest priority.", "We can deploy our system to production, or to end users, at any time, on demand." ] }, "architecture": { "title": "Architecture", "description": "Having a loosely coupled architecture allows your teams to work independently, without relying on other teams for support and services, which in turn enables them to work quickly and deliver value to the organization. It affects the extent to which a team can test and deploy their applications on demand, without requiring orchestration with other services.", "mean": 56, "profile-mean": 51, "url": "/devops-capabilities/technical/loosely-coupled-architecture/", "img": "https://www.google.com/images/icons/material/system/svg/grid_view_24px.svg", "context": "For the primary application or service you work on:", "questions": [ "On my team, we can make large-scale changes to the design of our system without the permission of somebody outside the team.", "To complete my own work, I don’t need to communicate and coordinate with people outside my team.", "On my team, we can make large-scale changes to the design of our system without creating significant work for other teams.", "On my team, we can make large-scale changes to the design of our system without depending on other teams to make changes in their systems.", "My team can deploy and release our product or service on demand, independently of other services it depends upon.", "We can do most of our testing on demand, without requiring an integrated test environment.", "On my team, we perform deployments during normal business hours with negligible downtime." ] } }, "low": { "conttest": { "title": "Continuous testing", "description": "Continuous testing means teams build quality in by testing throughout the software delivery lifecycle, using a combination of automated and manual tests to gain visibility into the quality of the software, and curating their test suites to make them more effective and to keep complexity and cost under control.", "mean": 63, "profile-mean": 53, "url": "/devops-capabilities/technical/test-automation/", "img": "https://www.google.com/images/icons/material/system/svg/assignment_turned_in_24px.svg", "context": "For the primary application or service you work on:", "questions": [ "Developers primarily create and maintain acceptance tests.", "When the automated tests pass, I am confident the software is releasable.", "Test failures are likely to indicate a real defect.", "It is easy for developers to fix acceptance test failures.", "Developers use their own development environment to reproduce acceptance failures.", "Automated tests are seamlessly integrated into our software delivery toolchain.", "We continuously review and improve our test suite to better find defects and keep complexity and cost under control.", "We have the test data necessary to run our tests easily at every step.", "Testers work alongside developers throughout the software development and delivery process.", "Manual test activities such as exploratory testing, usability testing, and acceptance testing are performed continuously throughout the delivery process.", "Developers practice test-driven development by writing unit tests before writing production code for all changes to the codebase.", "I can get feedback from automated tests in less than ten minutes both on my local workstation and from the CI server." ] }, "cloud": { "title": "Cloud infrastructure", "description": "We find that use of the cloud drives software delivery performance — but only if they follow the five essential characteristics of cloud computing as outlined by NIST: on-demand self-service, broad network access, resource pooling, rapid elasticity, and measured service.", "mean": 64, "profile-mean": 55, "url": "/devops-capabilities/technical/cloud-infrastructure/", "img": "https://www.google.com/images/icons/material/system/svg/cloud_24px.svg", "context": "Please rate how strongly you agree or disagree with the following statements.", "questions": [ "Once I have access, I can independently provision and configure the cloud resources and capabilities required for my product or service on demand without raising tickets or requiring human interaction.", "The service or product that I primarily work on is designed to be accessed from a broad range of devices (e.g. smartphones, tablets, laptops) over the network without the need for proprietary plug-ins or protocols.", "The cloud my product or service runs on serves multiple teams and applications, with compute and infrastructure resources dynamically assigned and re-assigned based on demand.", "I can dynamically increase or decrease the cloud resources available for the service or product that I primarily support based on demand.", "I can monitor or control the quantity and/or cost of cloud resources used by the service or product that I primarily support." ] }, "maintainability": { "title": "Code maintainability", "description": "Teams that manage code maintainability well have systems and tools that make it easy for developers to change code maintained by other teams, find examples in the codebase, reuse other people’s code, as well as add, upgrade, and migrate to new versions of dependencies without breaking their code.", "mean": 65, "profile-mean": 56, "url": "/devops-capabilities/technical/code-maintainability/", "img": "https://www.google.com/images/icons/material/system/svg/code_24px.svg", "context": "Please rate how strongly you agree or disagree with the following statements:", "questions": [ "It’s easy for us to change code maintained by other teams if we need to.", "It is easy for me to find examples in our codebase.", "It is easy for me to reuse other people’s code.", "It is easy for me to add new dependencies to my project.", "It is easy for me to migrate to a new version of a dependency.", "My dependencies are stable and rarely break my code." ] } } } ; /** * Compute performance data */ // constants are included from `constants_2019.js` via Hugo pipelines // helpers are included from `helpers_2019.js` via Hugo pipelines // user data analysis functions function getUserPerformanceIndicators(urlParams) { let userPerformanceIndicators = {}; Object.keys(indicators).forEach(indicator => { userPerformanceIndicators[indicator] = urlParams.get(indicator); }); return userPerformanceIndicators; } function getProfileAndPercentile(userPerformanceIndicators) { let average = 0; Object.keys(indicators).forEach(indicator => { indicator_value = userPerformanceIndicators[indicator]; let unnormalized = clamp(parseInt(indicator_value), 6); average += unnormalized; }) average = average / Object.keys(indicators).length; let profile = ''; for (let my_profile of Object.keys(profileStats)) { if (average <= profileStats[my_profile]) { profile = my_profile; break; } } let percentile = Math.round(100 * normDist(average, mean, stddev)); return { profile: profile, percentile: percentile } } function decoratePagewithProfileAndPercentage(userProfileAndPercentile) { Array.from(document.getElementsByClassName('profile-title')).forEach(element => { element.innerText = element.innerText.toLowerCase().replace('unknown',userProfileAndPercentile.profile); }) Array.from(document.getElementsByClassName('color-by-profile')).forEach(element => { element.classList.add(userProfileAndPercentile.profile); }) document.getElementById('percentile').innerText = userProfileAndPercentile.percentile; // highlight performance comparison table Object.keys(indicators).forEach((indicator) => { document.getElementById(indicator + '-' + userProfileAndPercentile.profile).classList.add('highlight'); }); } function drawUserPerformanceChart(percentile) { let yourPerformance = percentile / 100; let labels = ['Profile']; let values = ['']; let buffervalues = ['']; let prev = 0; for (let variable of Object.keys(profileStats)) { labels.push(variable.capitalize()); let percentile = variable == 'elite' ? 1 : normDist(profileStats[variable], mean, stddev); values.push(percentile - prev); buffervalues.push(null); prev = percentile; } labels.push('Your Performance'); values.push(yourPerformance); buffervalues.push(yourPerformance); let dataTable = google.visualization.arrayToDataTable([labels, buffervalues, values, buffervalues]); var options = { isStacked: true, bars: 'horizontal', bar: { groupWidth: '100%' }, height: 120, width: screen.width <= 480 ? 350 : '', chartArea: { top: 0, left: 20, right: 20, height: 60 }, enableInteractivity: false, series: { 0: { color: colors['low'] }, 1: { color: colors['medium'] }, 2: { color: colors['high'] }, 3: { color: colors['elite'] }, 4: { type: 'line', color: colors['you'], lineWidth: 4 } }, hAxis: { minValue: 0, maxValue: 1, ticks: [0, .25, .5, .75, 1], format: 'percent' }, legend: { position: 'bottom' } }; let chart = new google.visualization.BarChart(document.getElementById('performance-graph')); // move the "you" icon into place google.visualization.events.addListener( chart, 'ready', function() { var cli = chart.getChartLayoutInterface(); yourXpos = cli.getXLocation(dataTable.getValue(1, 5)); document.getElementById('yourPerformanceMarker').style.left = yourXpos-12 + 'px'; } ); chart.draw(dataTable, options); } function drawComparisonChart(indicator, user_score, industry, show_legend) { industry_baseline = baselines[industry]; user_score = scalePerf(user_score); let dataTable = new google.visualization.DataTable(); dataTable.addColumn('string', 'Aspect of Software Delivery Performance'); dataTable.addColumn('number', 'Your performance'); var pluralIndustry = industry=='all' ? 'ies' : 'y'; var industryString = `${industry} industr${pluralIndustry} performance`.capitalize() dataTable.addColumn('number', industryString); dataTable.addRows([[indicators[indicator].label, user_score, industry_baseline[indicator]]]); var options = { bars: 'horizontal', bar: { groupWidth: '30%' }, height: show_legend ? 120 : 70, width: screen.width <= 480 ? 350 : '', chartArea: { top: 0, left: 120, right: 20, height: 40 }, enableInteractivity: false, series: { 0: { color: colors['bar'] }, 1: { type: 'line', color: colors['average'], lineWidth: 0, pointSize: 12, pointShape: 'diamond'} }, hAxis: { minValue: 0, maxValue: 100, ticks: indicators[indicator]['ticks'] }, legend: { position: show_legend ? 'bottom' : 'off' } }; let target_div = (industry == 'all') ? 'all' : 'industry'; let chart = new google.visualization.BarChart(document.getElementById('perf-' + target_div + '-' + indicator)); // move the "you" icons into place icon_id = 'perf-' + target_div + '-' + indicator + '-marker'; google.visualization.events.addListener( chart, 'ready', function() { var cli = chart.getChartLayoutInterface(); yourXpos = cli.getXLocation(dataTable.getValue(0, 1)); document.getElementById(icon_id).style.left = yourXpos-12 + 'px'; } ); chart.draw(dataTable, options); } function populateKeyCapabilities(profile) { let selected_capabilities = allCapabilities[profile]; Object.keys(selected_capabilities).forEach((capability, index, arr) => { document.getElementById('rec-link-' + index).href = selected_capabilities[capability].url; document.getElementById('rec-img-' + index).src = selected_capabilities[capability].img; document.getElementById('rec-name-' + index).innerText = selected_capabilities[capability].title.capitalize(); }); } // "HELP ME PRIORITIZE" modal let capabilitiesSelected = [] let capabilityQuestions = {} let capabilityAnswers = {} const ranking = [ "Strongly disagree", "Disagree", "Neither agree nor disagree", "Agree", "Strongly agree" ] function updateStatus(e) { if (!e.target.name) { return; } capabilityQuestions[e.target.name] = true; let all_answered = true; for (let key of Object.keys(capabilityQuestions)) { if (!capabilityQuestions[key]) { all_answered = false; } } if (all_answered) { document.getElementById("modal-error").style.display = "none"; document.getElementById("results").disabled = false; } } function createCapabilitiesTable(profile) { let capTable = document.getElementById('modal-content'); // if table has already been built, don't rebuild if (document.getElementById('capabilities-table')) { return; }; let capabilities = allCapabilities[profile]; let questionId = 0; for (let key of Object.keys(capabilities)) { capability = capabilities[key]; capabilitiesSelected.push(key); capabilityAnswers[key] = []; html = `\n`; html += `

Capability ${questionId + 1} of 3: ${capability.questions.length} questions

\n`; html += `

${capability.title}

\n`; html += `

${capability.description} Learn more

\n`; html += '
'; html += ''; html += ``; let index = 0; for (let question of capability.questions) { html += ``; questionKey = key.toString() + "-" + index.toString(); for (let score = 0; score < 5; score++) { html += ``; } capabilityQuestions[questionKey] = false; html += ""; index++; } html += "
${capability.context} ${ranking[0]} ${ranking[1]} ${ranking[2]} ${ranking[3]} ${ranking[4]}

${question}

"; capTable.insertAdjacentHTML('beforeend', html) questionId++; } for (let key of Object.keys(capabilityQuestions)) { for (let element of document.getElementsByName(key)) { element.addEventListener("change", updateStatus); } } } function getRadioValue(name) { var elements = document.getElementsByName(name); for (var i = 0; i < elements.length; i++) { if (elements[i].checked) { return parseInt(elements[i].value); } } } function viewPerformance() { for (let key of Object.keys(capabilityQuestions)) { capability = key.substr(0, key.indexOf('-')); capabilityAnswers[capability].push(getRadioValue(key)); } let average = (array) => array.reduce((a, b) => a + b) / array.length; let queryString = ""; for (var i = 0; i < capabilitiesSelected.length; i++) { capability = capabilitiesSelected[i]; queryString += "&" + capability + "=" + average(capabilityAnswers[capability]).toFixed(2); } let newURL = window.location.search + queryString; window.location.replace(newURL); } function populateRecommendations(profile) { const urlParams = new URLSearchParams(window.location.search); let capabilities = allCapabilities[profile]; let performance = []; for (let capability of Object.keys(capabilities)) { let difference = 25 * parseFloat(urlParams.get(capability)); performance.push([capability, difference]); } performance.sort(function (first, second) { if (first[1] != second[1]) { return first[1] - second[1]; } return (first[1] - capabilities[first[0]]["profile-mean"]) - (second[1] - capabilities[second[0]]["profile-mean"]); }); let recommendation = document.getElementById("capability"); document.getElementById("caps_prioritized").style.display = "block"; document.getElementById("caps_unprioritized").style.display = "none"; for (let i = 0; i < 3; i++) { let capability = performance[i][0]; let title = document.getElementById("cap-title-" + i); title.textContent = capabilities[capability].title; if (i == 0) { recommendation.textContent = capabilities[capability].title.toLowerCase(); } let description = document.getElementById("cap-description-" + i); description.innerHTML = capabilities[capability].description; let url = document.getElementById("cap-url-" + i); url.href = capabilities[capability].url; renderCapabilityGraph(capability, capabilities[capability], i, i == 0, profile); } } function renderCapabilityGraph(capability, data, capability_index, focus, profile) { let element = `cap-graph-${capability_index}`; const urlParams = new URLSearchParams(window.location.search); let dataTable = new google.visualization.DataTable(); let value = parseFloat(urlParams.get(capability)) / 4.0; let mean = data["mean"] / 100; let profileMean = data["profile-mean"] / 100; let backgroundColor = focus ? '#E8F0FE' : 'white'; dataTable.addColumn('string', 'Capability'); dataTable.addColumn('number', 'Your performance'); dataTable.addColumn('number', 'Average (all industries)'); dataTable.addColumn('number', `${profile} performer avg.`.capitalize()); dataTable.addRows([[data["title"], value, mean, profileMean]]); var options = { bars: 'horizontal', bar: { groupWidth: '40%' }, height: 120, chartArea: { top: 0, left: 0, right: 20, height: 40 }, enableInteractivity: false, series: { 0: { color: colors['bar'] }, 1: { type: 'line', color: colors['average'], lineWidth: 0, pointSize: 8, pointShape: 'diamond' }, 2: { type: 'line', color: colors[profile], lineWidth: 0, pointSize: 8, pointShape: 'circle' } }, hAxis: { minValue: 0, maxValue: 1, ticks: [0, .25, .5, .75, 1], format: 'percent' }, vAxis: { textPosition: 'none' }, legend: { position: 'bottom' }, backgroundColor: backgroundColor }; let chart = new google.visualization.BarChart(document.getElementById(element)); // move the "you" icon into place google.visualization.events.addListener( chart, 'ready', function() { var cli = chart.getChartLayoutInterface(); yourXpos = cli.getXLocation(dataTable.getValue(0, 1)); document.getElementById(`capability-marker-${capability_index}`).style.left = yourXpos-12 + 'px'; } ); chart.draw(dataTable, options); } // compute metrics and render display (function() { // load charting library google.charts.load('current', { packages: ['corechart', 'bar'] }); // TODO: #38 test for presence of all URL Params and fail gracefully if any are missing. const urlParams = new URLSearchParams(window.location.search); // COMPUTE USER SCORES let industry = urlParams.get('industry'); let userPerformanceIndicators = getUserPerformanceIndicators(urlParams); let userProfileAndPercentile = getProfileAndPercentile(userPerformanceIndicators); console.debug(userPerformanceIndicators); // UPDATE PAGE BASED ON USER PROFILE decoratePagewithProfileAndPercentage(userProfileAndPercentile); populateKeyCapabilities(userProfileAndPercentile.profile); let industryBaselines = baselines[industry]; document.getElementById('cap-click').addEventListener('click', function(e) { e.preventDefault(); document.getElementById('modal').style.display='block'; createCapabilitiesTable(userProfileAndPercentile.profile); //Add event handler if the escape key is pressed document.addEventListener("keydown", (event) => { if (event.key === 'Escape') { document.getElementById('modal').style.display='none'; } }); }) document.getElementById('results').addEventListener('click', function(e) { e.preventDefault(); viewPerformance(); populateRecommendations(userProfileAndPercentile.profile, userPerformanceIndicators); }) // When charting library is loaded, render charts google.charts.setOnLoadCallback(function() { drawUserPerformanceChart(userProfileAndPercentile.percentile); // for each indicator, draw comparison chart Object.keys(indicators).forEach((indicator, index, arr) => { // draw 'all industries' drawComparisonChart( indicator, userPerformanceIndicators[indicator], 'all', arr[index + 1] ? false : true // show legend on last graph ); // draw 'your industry' drawComparisonChart( indicator, userPerformanceIndicators[indicator], industry, arr[index + 1] ? false : true // show legend on last graph ); }); // hide hidden divs until clicked document.getElementById('perf-industry').style.display='none'; document.getElementById('caps_prioritized').style.display='none'; // if capability recommendations have been generated, show them let capabilitiesInUrl = 0; Object.keys(allCapabilities).forEach((profile) => { Object.keys(allCapabilities[profile]).forEach( (capability) => { if (urlParams.has(capability)) { capabilitiesInUrl++; } }) }) if (capabilitiesInUrl == 3) { populateRecommendations(userProfileAndPercentile.profile, userPerformanceIndicators); } // hide other tabs until clicked let things_to_hide = ["c_compare","c_breakdown"] things_to_hide.forEach((tab) => { document.getElementById(tab).style.display='none'; }) // populate "share your results" textarea document.getElementById("share-url").textContent = location.href; }) }());