From b373d3d2ea46a0f634d24948f8662065741d2ae3 Mon Sep 17 00:00:00 2001 From: Joel Verhagen Date: Mon, 9 Apr 2018 11:57:32 -0700 Subject: [PATCH 01/32] Use JavaScript module pattern and 'use strict'; (#5786) Address https://github.com/NuGet/Engineering/issues/1313 --- src/NuGetGallery/App_Start/AppActivator.cs | 3 +- src/NuGetGallery/NuGetGallery.csproj | 2 - .../Scripts/gallery/async-file-upload.js | 614 +++++++++--------- src/NuGetGallery/Scripts/gallery/clamp.js | 2 + src/NuGetGallery/Scripts/gallery/common.js | 151 ++--- .../Scripts/gallery/page-edit-readme.js | 512 ++++++++------- .../Scripts/gallery/stats-dimensions.js | 260 ++++---- .../gallery/stats-perpackagestatsgraphs.js | 552 ++++++++-------- src/NuGetGallery/Scripts/nugetgallery.js | 18 +- src/NuGetGallery/Scripts/stats.js | 66 -- src/NuGetGallery/Scripts/statsgraphs.js | 165 ----- src/NuGetGallery/Scripts/supportrequests.js | 493 +++++++------- 12 files changed, 1325 insertions(+), 1513 deletions(-) delete mode 100644 src/NuGetGallery/Scripts/stats.js delete mode 100644 src/NuGetGallery/Scripts/statsgraphs.js diff --git a/src/NuGetGallery/App_Start/AppActivator.cs b/src/NuGetGallery/App_Start/AppActivator.cs index ee94934aea..3d510eb8f9 100644 --- a/src/NuGetGallery/App_Start/AppActivator.cs +++ b/src/NuGetGallery/App_Start/AppActivator.cs @@ -129,8 +129,7 @@ private static void BundlingPostStart() .Include("~/Scripts/jquery.validate.js") .Include("~/Scripts/jquery.validate.unobtrusive.js") .Include("~/Scripts/jquery.timeago.js") - .Include("~/Scripts/nugetgallery.js") - .Include("~/Scripts/stats.js"); + .Include("~/Scripts/nugetgallery.js"); BundleTable.Bundles.Add(scriptBundle); // Modernizr needs to be delivered at the top of the page but putting it in a bundle gets us a cache-buster. diff --git a/src/NuGetGallery/NuGetGallery.csproj b/src/NuGetGallery/NuGetGallery.csproj index edbbae2509..27a4d40e8c 100644 --- a/src/NuGetGallery/NuGetGallery.csproj +++ b/src/NuGetGallery/NuGetGallery.csproj @@ -2164,9 +2164,7 @@ Designer - - Designer diff --git a/src/NuGetGallery/Scripts/gallery/async-file-upload.js b/src/NuGetGallery/Scripts/gallery/async-file-upload.js index 6912818f94..a114e047ac 100644 --- a/src/NuGetGallery/Scripts/gallery/async-file-upload.js +++ b/src/NuGetGallery/Scripts/gallery/async-file-upload.js @@ -1,358 +1,360 @@ -'use strict'; - -var AsyncFileUploadManager = new function () { - var _actionUrl; - var _cancelUrl; - var _submitVerifyUrl; - var _isWebkitBrowser = false; // $.browser.webkit is not longer supported on jQuery - var _iframeId = '__fileUploadFrame'; - var _uploadFormId; - var _uploadFormData; - var _pollingInterval = 250; // in ms - var _pingUrl; - var _failureCount; - var _isUploadInProgress; - - this.init = function (pingUrl, formId, jQueryUrl, actionUrl, cancelUrl, submitVerifyUrl) { - _pingUrl = pingUrl; - _uploadFormId = formId; - _actionUrl = actionUrl; - _cancelUrl = cancelUrl; - _submitVerifyUrl = submitVerifyUrl; - - $('#file-select-feedback').on('dragenter', function (e) { - e.preventDefault(); - e.stopPropagation(); - - $(this).removeAttr('readonly'); - }); - - $('#file-select-feedback').on('dragleave', function (e) { - e.preventDefault(); - e.stopPropagation(); - - $(this).attr('readonly', 'readonly'); - }); - - - $('#file-select-feedback').on('dragover', function (e) { - e.preventDefault(); - e.stopPropagation(); - }); - - - $('#file-select-feedback').on('drop', function (e) { - e.preventDefault(); - e.stopPropagation(); - $(this).attr('readonly', 'readonly'); +var AsyncFileUploadManager = (function () { + 'use strict'; + + return new function () { + var _actionUrl; + var _cancelUrl; + var _submitVerifyUrl; + var _isWebkitBrowser = false; // $.browser.webkit is not longer supported on jQuery + var _iframeId = '__fileUploadFrame'; + var _uploadFormId; + var _uploadFormData; + var _pollingInterval = 250; // in ms + var _pingUrl; + var _failureCount; + var _isUploadInProgress; + + this.init = function (pingUrl, formId, jQueryUrl, actionUrl, cancelUrl, submitVerifyUrl) { + _pingUrl = pingUrl; + _uploadFormId = formId; + _actionUrl = actionUrl; + _cancelUrl = cancelUrl; + _submitVerifyUrl = submitVerifyUrl; + + $('#file-select-feedback').on('dragenter', function (e) { + e.preventDefault(); + e.stopPropagation(); + + $(this).removeAttr('readonly'); + }); - clearErrors(); - var droppedFile = e.originalEvent.dataTransfer.files[0]; - $('#file-select-feedback').attr('value', droppedFile.name); + $('#file-select-feedback').on('dragleave', function (e) { + e.preventDefault(); + e.stopPropagation(); - prepareUploadFormData(); - _uploadFormData.set("UploadFile", droppedFile); - cancelUploadAsync(startUploadAsync, startUploadAsync); - }); + $(this).attr('readonly', 'readonly'); + }); - $('#file-select-feedback').on('click', function () { - $('#input-select-file').click(); - }); - $('#input-select-file').on('change', function () { - clearErrors(); - var fileName = window.nuget.getFileName($('#input-select-file').val()); + $('#file-select-feedback').on('dragover', function (e) { + e.preventDefault(); + e.stopPropagation(); + }); + + + $('#file-select-feedback').on('drop', function (e) { + e.preventDefault(); + e.stopPropagation(); + $(this).attr('readonly', 'readonly'); + + clearErrors(); + var droppedFile = e.originalEvent.dataTransfer.files[0]; + $('#file-select-feedback').attr('value', droppedFile.name); - if (fileName.length > 0) { - $('#file-select-feedback').attr('value', fileName); prepareUploadFormData(); - // Cancel any ongoing upload, and then start the new upload. - // If the cancel fails, still try to upload the new one. + _uploadFormData.set("UploadFile", droppedFile); cancelUploadAsync(startUploadAsync, startUploadAsync); - } else { - resetFileSelectFeedback(); + }); + + $('#file-select-feedback').on('click', function () { + $('#input-select-file').click(); + }); + + $('#input-select-file').on('change', function () { + clearErrors(); + var fileName = window.nuget.getFileName($('#input-select-file').val()); + + if (fileName.length > 0) { + $('#file-select-feedback').attr('value', fileName); + prepareUploadFormData(); + // Cancel any ongoing upload, and then start the new upload. + // If the cancel fails, still try to upload the new one. + cancelUploadAsync(startUploadAsync, startUploadAsync); + } else { + resetFileSelectFeedback(); + } + }); + + if (InProgressPackage != null) { + bindData(InProgressPackage); } - }); + } - if (InProgressPackage != null) { - bindData(InProgressPackage); + function resetFileSelectFeedback() { + $('#file-select-feedback').attr('value', 'Browse or Drop files to select a package...'); } - } - function resetFileSelectFeedback() { - $('#file-select-feedback').attr('value', 'Browse or Drop files to select a package...'); - } + function prepareUploadFormData() { + var formData = new FormData($('#' + _uploadFormId)[0]); + _uploadFormData = formData; + } - function prepareUploadFormData() { - var formData = new FormData($('#' + _uploadFormId)[0]); - _uploadFormData = formData; - } + function startUploadAsync(callback, error) { + // Shortcut the upload if the nupkg input doesn't have a value + if ($('#input-select-file').val() == null) { + return; + } - function startUploadAsync(callback, error) { - // Shortcut the upload if the nupkg input doesn't have a value - if ($('#input-select-file').val() == null) { - return; - } + startProgressBar(); - startProgressBar(); + $.ajax({ + url: _actionUrl, + type: 'POST', - $.ajax({ - url: _actionUrl, - type: 'POST', + data: _uploadFormData, - data: _uploadFormData, + cache: false, + contentType: false, + processData: false, - cache: false, - contentType: false, - processData: false, + success: function (model, resultCodeString, fullResponse) { + bindData(model); + endProgressBar(); + if (callback) { + callback(); + } + }, - success: function (model, resultCodeString, fullResponse) { - bindData(model); - endProgressBar(); - if (callback) { - callback(); - } - }, + error: handleErrors.bind(this, error) + }); + } - error: handleErrors.bind(this, error) - }); - } + function submitVerifyAsync(callback, error) { + $.ajax({ + url: _submitVerifyUrl, + type: 'POST', - function submitVerifyAsync(callback, error) { - $.ajax({ - url: _submitVerifyUrl, - type: 'POST', + data: new FormData($('#verify-metadata-form')[0]), - data: new FormData($('#verify-metadata-form')[0]), + cache: false, + contentType: false, + processData: false, - cache: false, - contentType: false, - processData: false, + success: function (model, resultCodeString, fullResponse) { + if (callback) { + callback(model); + } + }, - success: function (model, resultCodeString, fullResponse) { - if (callback) { - callback(model); - } - }, + error: handleErrors.bind(this, error) + }); + } - error: handleErrors.bind(this, error) - }); - } + function cancelUploadAsync(callback, error) { + clearErrors(); - function cancelUploadAsync(callback, error) { - clearErrors(); + $.ajax({ + url: _cancelUrl, + type: 'POST', - $.ajax({ - url: _cancelUrl, - type: 'POST', + data: new FormData($('#cancel-form')[0]), - data: new FormData($('#cancel-form')[0]), + cache: false, + contentType: false, + processData: false, - cache: false, - contentType: false, - processData: false, + success: function (model, resultCodeString, fullResponse) { + bindData(model); + if (callback) { + callback(); + } + }, - success: function (model, resultCodeString, fullResponse) { - bindData(model); - if (callback) { - callback(); - } - }, - - error: handleErrors.bind(this, error) - }); - } - - function handleErrors(errorCallback, model, resultCodeString, fullResponse) { - bindData(null); - - switch (resultCodeString) { - case "timeout": - displayErrors(["The operation timed out. Please try again."]); - break; - case "abort": - displayErrors(["The operation was aborted. Please try again."]); - break; - default: - displayErrors(model.responseJSON); - break; + error: handleErrors.bind(this, error) + }); } - if (fullResponse.status >= 500) { - displayErrors(["There was a server error."]); - } + function handleErrors(errorCallback, model, resultCodeString, fullResponse) { + bindData(null); + + switch (resultCodeString) { + case "timeout": + displayErrors(["The operation timed out. Please try again."]); + break; + case "abort": + displayErrors(["The operation was aborted. Please try again."]); + break; + default: + displayErrors(model.responseJSON); + break; + } - endProgressBar(); - if (errorCallback) { - errorCallback(); - } - } + if (fullResponse.status >= 500) { + displayErrors(["There was a server error."]); + } - function displayErrors(errors) { - if (errors == null || errors.length < 1) { - return; + endProgressBar(); + if (errorCallback) { + errorCallback(); + } } - clearErrors() - - var failureContainer = $("#validation-failure-container"); - var failureListContainer = document.createElement("div"); - $(failureListContainer).attr("id", "validation-failure-list"); - $(failureListContainer).attr("data-bind", "template: { name: 'validation-errors', data: data }"); - failureContainer.append(failureListContainer); - ko.applyBindings({ data: errors }, failureListContainer); - failureContainer.removeClass("hidden"); - } - - function clearErrors() { - $("#validation-failure-container").addClass("hidden"); - $("#validation-failure-list").remove(); - - var warnings = $('#warning-container'); - warnings.addClass("hidden"); - warnings.children().remove(); - } - - function bindData(model) { - $("#verify-package-block").remove(); - $("#submit-block").remove(); - $("#verify-warning-container").addClass("hidden"); - $("#verify-collapser-container").addClass("hidden"); - $("#submit-collapser-container").addClass("hidden"); - - if (model != null) { - var reportContainerElement = document.createElement("div"); - $(reportContainerElement).attr("id", "verify-package-block"); - $(reportContainerElement).attr("class", "collapse in"); - $(reportContainerElement).attr("aria-expanded", "true"); - $(reportContainerElement).attr("data-bind", "template: { name: 'verify-metadata-template', data: data }"); - $("#verify-package-container").append(reportContainerElement); - ko.applyBindings({ data: model }, reportContainerElement); - - var submitContainerElement = document.createElement("div"); - $(submitContainerElement).attr("id", "submit-block"); - $(submitContainerElement).attr("class", "collapse in"); - $(submitContainerElement).attr("aria-expanded", "true"); - $(submitContainerElement).attr("data-bind", "template: { name: 'submit-package-template', data: data }"); - $("#submit-package-container").append(submitContainerElement); - ko.applyBindings({ data: model }, submitContainerElement); - - $('#verify-cancel-button').on('click', function () { - $('#verify-cancel-button').attr('disabled', 'disabled'); - $('#verify-cancel-button').attr('value', 'Cancelling'); - $('#verify-cancel-button').addClass('.loading'); - $('#verify-submit-button').attr('disabled', 'disabled'); - $('#input-select-file').val(""); - resetFileSelectFeedback(); - cancelUploadAsync(); - }); + function displayErrors(errors) { + if (errors == null || errors.length < 1) { + return; + } - $('#verify-submit-button').on('click', function () { - $('#verify-cancel-button').attr('disabled', 'disabled'); - $('#verify-submit-button').attr('disabled', 'disabled'); - $('#verify-submit-button').attr('value', 'Submitting'); - $('#verify-submit-button').addClass('.loading'); - submitVerifyAsync(navigateToPage, bindData.bind(this, model)); - }); + clearErrors() - $('#iconurl-field').on('change', function () { - $('#icon-preview').attr('src', $('#iconurl-field').val()); - }); + var failureContainer = $("#validation-failure-container"); + var failureListContainer = document.createElement("div"); + $(failureListContainer).attr("id", "validation-failure-list"); + $(failureListContainer).attr("data-bind", "template: { name: 'validation-errors', data: data }"); + failureContainer.append(failureListContainer); + ko.applyBindings({ data: errors }, failureListContainer); + failureContainer.removeClass("hidden"); + } - $("#verify-warning-container").removeClass("hidden"); - $("#verify-collapser-container").removeClass("hidden"); - $("#submit-collapser-container").removeClass("hidden"); + function clearErrors() { + $("#validation-failure-container").addClass("hidden"); + $("#validation-failure-list").remove(); - window.nuget.configureExpanderHeading("verify-package-section"); - window.nuget.configureExpanderHeading("submit-package-form"); + var warnings = $('#warning-container'); + warnings.addClass("hidden"); + warnings.children().remove(); } - bindReadMeData(model); - } - - function navigateToPage(verifyResponse) { - document.location = verifyResponse.location; - } - - function startProgressBar() { - _isUploadInProgress = true; - _failureCount = 0; - - setProgressIndicator(0, ''); - $("#upload-progress-bar-container").removeClass("hidden"); - setTimeout(getProgress, 100); - } - - function endProgressBar() { - $("#upload-progress-bar-container").addClass("hidden"); - _isUploadInProgress = false; - } - - function getProgress() { - $.ajax({ - type: 'GET', - dataType: 'json', - url: _pingUrl, - success: onGetProgressSuccess, - error: onGetProgressError - }); - } - - function onGetProgressSuccess(result) { - if (!result) { - return; - } + function bindData(model) { + $("#verify-package-block").remove(); + $("#submit-block").remove(); + $("#verify-warning-container").addClass("hidden"); + $("#verify-collapser-container").addClass("hidden"); + $("#submit-collapser-container").addClass("hidden"); + + if (model != null) { + var reportContainerElement = document.createElement("div"); + $(reportContainerElement).attr("id", "verify-package-block"); + $(reportContainerElement).attr("class", "collapse in"); + $(reportContainerElement).attr("aria-expanded", "true"); + $(reportContainerElement).attr("data-bind", "template: { name: 'verify-metadata-template', data: data }"); + $("#verify-package-container").append(reportContainerElement); + ko.applyBindings({ data: model }, reportContainerElement); + + var submitContainerElement = document.createElement("div"); + $(submitContainerElement).attr("id", "submit-block"); + $(submitContainerElement).attr("class", "collapse in"); + $(submitContainerElement).attr("aria-expanded", "true"); + $(submitContainerElement).attr("data-bind", "template: { name: 'submit-package-template', data: data }"); + $("#submit-package-container").append(submitContainerElement); + ko.applyBindings({ data: model }, submitContainerElement); + + $('#verify-cancel-button').on('click', function () { + $('#verify-cancel-button').attr('disabled', 'disabled'); + $('#verify-cancel-button').attr('value', 'Cancelling'); + $('#verify-cancel-button').addClass('.loading'); + $('#verify-submit-button').attr('disabled', 'disabled'); + $('#input-select-file').val(""); + resetFileSelectFeedback(); + cancelUploadAsync(); + }); + + $('#verify-submit-button').on('click', function () { + $('#verify-cancel-button').attr('disabled', 'disabled'); + $('#verify-submit-button').attr('disabled', 'disabled'); + $('#verify-submit-button').attr('value', 'Submitting'); + $('#verify-submit-button').addClass('.loading'); + submitVerifyAsync(navigateToPage, bindData.bind(this, model)); + }); + + $('#iconurl-field').on('change', function () { + $('#icon-preview').attr('src', $('#iconurl-field').val()); + }); + + $("#verify-warning-container").removeClass("hidden"); + $("#verify-collapser-container").removeClass("hidden"); + $("#submit-collapser-container").removeClass("hidden"); + + window.nuget.configureExpanderHeading("verify-package-section"); + window.nuget.configureExpanderHeading("submit-package-form"); + } - var percent = result.Progress; + bindReadMeData(model); + } - if (!result.FileName) { - return; + function navigateToPage(verifyResponse) { + document.location = verifyResponse.location; } - setProgressIndicator(percent, result.FileName); - if (percent < 100) { - setTimeout(getProgress, _pollingInterval); + function startProgressBar() { + _isUploadInProgress = true; + _failureCount = 0; + + setProgressIndicator(0, ''); + $("#upload-progress-bar-container").removeClass("hidden"); + setTimeout(getProgress, 100); } - else { + + function endProgressBar() { + $("#upload-progress-bar-container").addClass("hidden"); _isUploadInProgress = false; } - } - function onGetProgressError(result) { - if (++_failureCount < 3) { - setTimeout(getProgress, _pollingInterval); + function getProgress() { + $.ajax({ + type: 'GET', + dataType: 'json', + url: _pingUrl, + success: onGetProgressSuccess, + error: onGetProgressError + }); + } + + function onGetProgressSuccess(result) { + if (!result) { + return; + } + + var percent = result.Progress; + + if (!result.FileName) { + return; + } + + setProgressIndicator(percent, result.FileName); + if (percent < 100) { + setTimeout(getProgress, _pollingInterval); + } + else { + _isUploadInProgress = false; + } + } + + function onGetProgressError(result) { + if (++_failureCount < 3) { + setTimeout(getProgress, _pollingInterval); + } } - } - - function setProgressIndicator(percentComplete, fileName) { - $("#upload-progress-bar").width(percentComplete + "%") - .attr("aria-valuenow", percentComplete) - .text(percentComplete + "%"); - } - - // obsolete - function constructIframe(jQueryUrl) { - var iframe = document.getElementById(_iframeId); - if (iframe) { - return; + + function setProgressIndicator(percentComplete, fileName) { + $("#upload-progress-bar").width(percentComplete + "%") + .attr("aria-valuenow", percentComplete) + .text(percentComplete + "%"); } - iframe = document.createElement('iframe'); - iframe.setAttribute('id', _iframeId); - iframe.setAttribute('style', 'display: none; visibility: hidden;'); - - $(iframe).load(function () { - var scriptRef = document.createElement('script'); - scriptRef.setAttribute("src", jQueryUrl); - scriptRef.setAttribute("type", "text/javascript"); - iframe.contentDocument.body.appendChild(scriptRef); - - var scriptContent = document.createElement('script'); - scriptContent.setAttribute("type", "text/javascript"); - scriptContent.innerHTML = "var _callback,_error, _key, _pingUrl, _fcount;function start(b,c,e){_callback=c;_pingUrl=b;_error=e;_fcount=0;setTimeout(getProgress,200)}function getProgress(){$.ajax({type:'GET',dataType:'json',url:_pingUrl,success:onSuccess,error:_error})}function onSuccess(a){if(!a){return}var b=a.Progress;var d=a.FileName;if(!d){return}_callback(b,d);if(b<100){setTimeout(getProgress,200)}}function onError(a){if(++_fcount<3){setTimeout(getProgress,200)}}"; - iframe.contentDocument.body.appendChild(scriptContent); - }); - - document.body.appendChild(iframe); - } -}; \ No newline at end of file + // obsolete + function constructIframe(jQueryUrl) { + var iframe = document.getElementById(_iframeId); + if (iframe) { + return; + } + + iframe = document.createElement('iframe'); + iframe.setAttribute('id', _iframeId); + iframe.setAttribute('style', 'display: none; visibility: hidden;'); + + $(iframe).load(function () { + var scriptRef = document.createElement('script'); + scriptRef.setAttribute("src", jQueryUrl); + scriptRef.setAttribute("type", "text/javascript"); + iframe.contentDocument.body.appendChild(scriptRef); + + var scriptContent = document.createElement('script'); + scriptContent.setAttribute("type", "text/javascript"); + scriptContent.innerHTML = "var _callback,_error, _key, _pingUrl, _fcount;function start(b,c,e){_callback=c;_pingUrl=b;_error=e;_fcount=0;setTimeout(getProgress,200)}function getProgress(){$.ajax({type:'GET',dataType:'json',url:_pingUrl,success:onSuccess,error:_error})}function onSuccess(a){if(!a){return}var b=a.Progress;var d=a.FileName;if(!d){return}_callback(b,d);if(b<100){setTimeout(getProgress,200)}}function onError(a){if(++_fcount<3){setTimeout(getProgress,200)}}"; + iframe.contentDocument.body.appendChild(scriptContent); + }); + + document.body.appendChild(iframe); + } + }; +}()); diff --git a/src/NuGetGallery/Scripts/gallery/clamp.js b/src/NuGetGallery/Scripts/gallery/clamp.js index a2c9814070..fdbb5510ee 100644 --- a/src/NuGetGallery/Scripts/gallery/clamp.js +++ b/src/NuGetGallery/Scripts/gallery/clamp.js @@ -7,6 +7,7 @@ */ (function(root, factory) { + 'use strict'; if (typeof define === 'function' && define.amd) { // AMD define([], factory); @@ -18,6 +19,7 @@ root.$clamp = factory(); } }(this, function() { + 'use strict'; /** * Clamps a text node. * @param {HTMLElement} element. Element containing the text node to clamp. diff --git a/src/NuGetGallery/Scripts/gallery/common.js b/src/NuGetGallery/Scripts/gallery/common.js index 427b31d4bb..e3e778eb05 100644 --- a/src/NuGetGallery/Scripts/gallery/common.js +++ b/src/NuGetGallery/Scripts/gallery/common.js @@ -280,88 +280,89 @@ window.nuget = nuget; initializeJQueryValidator(); -})(); - -$(function () { - // Use moment.js to format attributes with the "datetime" attribute to "X time ago". - $.each($('*[data-datetime]'), function () { - var $el = $(this); - var formats = window.nuget.getDateFormats($el.data().datetime); - if (!formats) { - return; - } - if (!$el.attr('title')) { - $el.attr('title', formats.title); - } + $(function () { + // Use moment.js to format attributes with the "datetime" attribute to "X time ago". + $.each($('*[data-datetime]'), function () { + var $el = $(this); + var formats = window.nuget.getDateFormats($el.data().datetime); + if (!formats) { + return; + } - if (formats.text) { - $el.text(formats.text); - } - }); + if (!$el.attr('title')) { + $el.attr('title', formats.title); + } - // Handle confirm pop-ups. - $('*[data-confirm]').delegate('', 'click', function (e) { - window.nuget.confirmEvent($(this).data().confirm, e); - }); + if (formats.text) { + $el.text(formats.text); + } + }); - // Select the first input that has an error. - $('.has-error') - .find('input,textarea,select') - .filter(':visible:first') - .focus(); - - // Handle Google analytics tracking event on specific links. - $.each($('a[data-track]'), function () { - $(this).click(function (e) { - var href = $(this).attr('href'); - var category = $(this).data().track; - if (window.nuget.isGaAvailable() && href && category) { - if (e.altKey || e.ctrlKey || e.metaKey) { - ga('send', 'event', category, 'click', href); - } else { - e.preventDefault(); - ga('send', 'event', category, 'click', href, { - 'transport': 'beacon', - 'hitCallback': window.nuget.createFunctionWithTimeout(function () { - document.location = href; - }) - }); + // Handle confirm pop-ups. + $('*[data-confirm]').delegate('', 'click', function (e) { + window.nuget.confirmEvent($(this).data().confirm, e); + }); + + // Select the first input that has an error. + $('.has-error') + .find('input,textarea,select') + .filter(':visible:first') + .focus(); + + // Handle Google analytics tracking event on specific links. + $.each($('a[data-track]'), function () { + $(this).click(function (e) { + var href = $(this).attr('href'); + var category = $(this).data().track; + if (window.nuget.isGaAvailable() && href && category) { + if (e.altKey || e.ctrlKey || e.metaKey) { + ga('send', 'event', category, 'click', href); + } else { + e.preventDefault(); + ga('send', 'event', category, 'click', href, { + 'transport': 'beacon', + 'hitCallback': window.nuget.createFunctionWithTimeout(function () { + document.location = href; + }) + }); + } } - } + }); }); - }); - // Show elements that require ClickOnce - (function () { - var userAgent = window.navigator.userAgent.toUpperCase(); - var hasNativeDotNet = userAgent.indexOf('.NET CLR 3.5') >= 0; - if (hasNativeDotNet) { - $('.no-clickonce').removeClass('no-clickonce'); - } - })(); + // Show elements that require ClickOnce + (function () { + var userAgent = window.navigator.userAgent.toUpperCase(); + var hasNativeDotNet = userAgent.indexOf('.NET CLR 3.5') >= 0; + if (hasNativeDotNet) { + $('.no-clickonce').removeClass('no-clickonce'); + } + })(); - // Don't close the dropdown on click events inside of the dropdown. - $(document).on('click', '.dropdown-menu', function (e) { - e.stopPropagation(); - }); + // Don't close the dropdown on click events inside of the dropdown. + $(document).on('click', '.dropdown-menu', function (e) { + e.stopPropagation(); + }); - $(document).on('keydown', function (e) { - var code = (e.keyCode || e.which); - var isValidInputCharacter = - ((code >= 48 && code <= 57) // numbers 0-9 - || (code >= 64 && code <= 90) // letters a-z - || (code >= 96 && code <= 111) // numpad - || (code >= 186 && code <= 192) // ; = , - . / ` - || (code >= 219 && code <= 222)) // [\ ] ' - && !e.altKey && !e.ctrlKey && !e.metaKey; - - if (isValidInputCharacter && document.activeElement == document.body) { - var searchbox = $("#search"); - searchbox.focus(); - var currInput = searchbox.val(); - searchbox.val(""); - searchbox.val(currInput); - } + $(document).on('keydown', function (e) { + var code = (e.keyCode || e.which); + var isValidInputCharacter = + ((code >= 48 && code <= 57) // numbers 0-9 + || (code >= 64 && code <= 90) // letters a-z + || (code >= 96 && code <= 111) // numpad + || (code >= 186 && code <= 192) // ; = , - . / ` + || (code >= 219 && code <= 222)) // [\ ] ' + && !e.altKey && !e.ctrlKey && !e.metaKey; + + if (isValidInputCharacter && document.activeElement == document.body) { + var searchbox = $("#search"); + searchbox.focus(); + var currInput = searchbox.val(); + searchbox.val(""); + searchbox.val(currInput); + } + }); }); -}); +}()); + diff --git a/src/NuGetGallery/Scripts/gallery/page-edit-readme.js b/src/NuGetGallery/Scripts/gallery/page-edit-readme.js index c8ccf2cf67..317d27beff 100644 --- a/src/NuGetGallery/Scripts/gallery/page-edit-readme.js +++ b/src/NuGetGallery/Scripts/gallery/page-edit-readme.js @@ -1,307 +1,315 @@ -'use strict'; - -var EditReadmeManager = new function () { - var _currVersion; - var _viewModel; - var _changedState; - var _submitUrl; - var _cancelUrl; - var _submitting; - var _submitted = true; - - this.init = function (model, submitUrl, cancelUrl) { - _submitting = false; - _submitted = false; - _submitUrl = submitUrl; - _cancelUrl = cancelUrl; - _viewModel = model; - _changedState = {}; - bindData(_viewModel); - - $(window).on('beforeunload', confirmLeave); - - $('#verify-submit-button').attr('disabled', 'disabled'); - - $('#input-select-version').on('change', function () { - document.location = $(this).val(); - }); +var EditReadmeManager = (function () { + 'use strict'; + + return new function () { + var _currVersion; + var _viewModel; + var _changedState; + var _submitUrl; + var _cancelUrl; + var _submitting; + var _submitted = true; + + this.init = function (model, submitUrl, cancelUrl) { + _submitting = false; + _submitted = false; + _submitUrl = submitUrl; + _cancelUrl = cancelUrl; + _viewModel = model; + _changedState = {}; + bindData(_viewModel); + + $(window).on('beforeunload', confirmLeave); - $('input[type="text"], input[type="checkbox"], input[type="url"], input[type="file"], textarea').on('change keydown', function () { - $(this).addClass("edited"); - _changedState[$(this).attr('id')] = true; - $('#verify-submit-button').removeAttr('disabled'); - }); - } + $('#verify-submit-button').attr('disabled', 'disabled'); - this.isEdited = function () { - return Object.keys(_changedState).reduce(function (previous, key) { return previous || _changedState[key]; }, false); - } + $('#input-select-version').on('change', function () { + document.location = $(this).val(); + }); - function confirmLeave() { - var message = ""; - if (_submitting) { - message = "Your request is being submitted. Are you sure you want to leave?"; + $('input[type="text"], input[type="checkbox"], input[type="url"], input[type="file"], textarea').on('change keydown', function () { + $(this).addClass("edited"); + _changedState[$(this).attr('id')] = true; + $('#verify-submit-button').removeAttr('disabled'); + }); } - if (message !== "") { - return message; + this.isEdited = function () { + return Object.keys(_changedState).reduce(function (previous, key) { return previous || _changedState[key]; }, false); } - } - function submitAsync(callback, error) { - if (EditReadmeManager.isEdited()) { - if (!_submitting) { - _submitting = true; - $.ajax({ - url: _submitUrl, - type: 'POST', - - data: new FormData($('#edit-readme-form')[0]), - - cache: false, - contentType: false, - processData: false, - - success: function (model, resultCodeString, fullResponse) { - _submitting = false; - _submitted = true; - if (callback) { - callback(model); - } - }, - - error: handleErrors.bind(this, error) - }); + function confirmLeave() { + var message = ""; + if (_submitting) { + message = "Your request is being submitted. Are you sure you want to leave?"; } - } else { - if (callback) { - callback(); + + if (message !== "") { + return message; } } - } - function cancelEdit() { - navigateToPage({ location: _cancelUrl }); - } + function submitAsync(callback, error) { + if (EditReadmeManager.isEdited()) { + if (!_submitting) { + _submitting = true; + $.ajax({ + url: _submitUrl, + type: 'POST', + + data: new FormData($('#edit-readme-form')[0]), + + cache: false, + contentType: false, + processData: false, + + success: function (model, resultCodeString, fullResponse) { + _submitting = false; + _submitted = true; + if (callback) { + callback(model); + } + }, + + error: handleErrors.bind(this, error) + }); + } + } else { + if (callback) { + callback(); + } + } + } - function navigateToPage(editReadmeResponse) { - document.location = editReadmeResponse.location; - } + function cancelEdit() { + navigateToPage({ location: _cancelUrl }); + } - function displayErrors(errors) { - if (errors == null || errors.length < 1) { - return; + function navigateToPage(editReadmeResponse) { + document.location = editReadmeResponse.location; } - var failureContainer = $("#validation-failure-container"); - var failureListContainer = document.createElement("div"); - $(failureListContainer).attr("id", "validation-failure-list"); - $(failureListContainer).attr("data-bind", "template: { name: 'validation-errors', data: data }"); - failureContainer.append(failureListContainer); - ko.applyBindings({ data: errors }, failureListContainer); + function displayErrors(errors) { + if (errors == null || errors.length < 1) { + return; + } - failureContainer.removeClass("hidden"); - } + var failureContainer = $("#validation-failure-container"); + var failureListContainer = document.createElement("div"); + $(failureListContainer).attr("id", "validation-failure-list"); + $(failureListContainer).attr("data-bind", "template: { name: 'validation-errors', data: data }"); + failureContainer.append(failureListContainer); + ko.applyBindings({ data: errors }, failureListContainer); - function handleErrors(errorCallback, model, resultCodeString, fullResponse) { - bindData(null); - - _submitting = false; - switch (resultCodeString) { - case "timeout": - displayErrors(["The operation timed out. Please try again."]) - break; - case "abort": - displayErrors(["The operation was aborted. Please try again."]) - break; - default: - displayErrors(model.responseJSON); - break; + failureContainer.removeClass("hidden"); } - if ((fullResponse && fullResponse.status >= 500) || (model && model.status >= 500)) { - displayErrors(["There was a server error."]) + function handleErrors(errorCallback, model, resultCodeString, fullResponse) { + bindData(null); + + _submitting = false; + switch (resultCodeString) { + case "timeout": + displayErrors(["The operation timed out. Please try again."]) + break; + case "abort": + displayErrors(["The operation was aborted. Please try again."]) + break; + default: + displayErrors(model.responseJSON); + break; + } + + if ((fullResponse && fullResponse.status >= 500) || (model && model.status >= 500)) { + displayErrors(["There was a server error."]) + } + + if (errorCallback) { + errorCallback(); + } } - if (errorCallback) { - errorCallback(); + function bindData(model) { + + if (model == null) { + return; + } + + var submitContainerElement = document.createElement("div"); + $(submitContainerElement).attr("id", "submit-block"); + $(submitContainerElement).attr("class", "collapse in"); + $(submitContainerElement).attr("aria-expanded", "true"); + $(submitContainerElement).attr("data-bind", "template: { name: 'submit-package-template', data: data }"); + $("#submit-package-container").append(submitContainerElement); + ko.applyBindings({ data: model }, submitContainerElement); + + $('#verify-cancel-button').on('click', function () { + cancelEdit(); + }); + + $('#verify-submit-button').on('click', function () { + $('#verify-cancel-button').attr('disabled', 'disabled'); + $('#verify-submit-button').attr('disabled', 'disabled'); + $('#verify-submit-button').attr('value', 'Submitting'); + $('#verify-submit-button').addClass('.loading'); + submitAsync(navigateToPage); + }); + + bindReadMeData(model); } - } + }; +}()); + +var bindReadMeData = (function () { + 'use strict'; - function bindData(model) { - - if (model == null) { + function bindReadMeData(model) { + $("#import-readme-block").remove(); + $("#readme-collapser-container").addClass("hidden"); + + if (model == null) + { return; } - var submitContainerElement = document.createElement("div"); - $(submitContainerElement).attr("id", "submit-block"); - $(submitContainerElement).attr("class", "collapse in"); - $(submitContainerElement).attr("aria-expanded", "true"); - $(submitContainerElement).attr("data-bind", "template: { name: 'submit-package-template', data: data }"); - $("#submit-package-container").append(submitContainerElement); - ko.applyBindings({ data: model }, submitContainerElement); + model.SelectedTab = ko.observable('written'); + model.OnReadmeTabChange = function (_, e) { + model.SelectedTab($(e.target).data('source-type')); + return true; + }; - $('#verify-cancel-button').on('click', function () { - cancelEdit(); - }); + var readMeContainerElement = document.createElement("div"); + $(readMeContainerElement).attr("id", "import-readme-block"); + $(readMeContainerElement).attr("class", "collapse in"); + $(readMeContainerElement).attr("aria-expanded", "true"); + $(readMeContainerElement).attr("data-bind", "template: { name: 'import-readme-template', data: data }"); + $("#import-readme-container").append(readMeContainerElement); + ko.applyBindings({ data: model }, readMeContainerElement); - $('#verify-submit-button').on('click', function () { - $('#verify-cancel-button').attr('disabled', 'disabled'); - $('#verify-submit-button').attr('disabled', 'disabled'); - $('#verify-submit-button').attr('value', 'Submitting'); - $('#verify-submit-button').addClass('.loading'); - submitAsync(navigateToPage); - }); + $("#readme-collapser-container").removeClass("hidden"); - bindReadMeData(model); - } -} + window.nuget.configureExpanderHeading("readme-package-form"); -function bindReadMeData(model) { - $("#import-readme-block").remove(); - $("#readme-collapser-container").addClass("hidden"); + $("#ReadMeUrlInput").on("change blur", function () { + clearReadMeError(); + }); - if (model == null) - { - return; - } + $('#ReadMeFileText').on('click', function () { + $('#ReadMeFileInput').click(); + }); - model.SelectedTab = ko.observable('written'); - model.OnReadmeTabChange = function (_, e) { - model.SelectedTab($(e.target).data('source-type')); - return true; - }; + $('#ReadMeFileInput').on('change', function () { + clearReadMeError(); - var readMeContainerElement = document.createElement("div"); - $(readMeContainerElement).attr("id", "import-readme-block"); - $(readMeContainerElement).attr("class", "collapse in"); - $(readMeContainerElement).attr("aria-expanded", "true"); - $(readMeContainerElement).attr("data-bind", "template: { name: 'import-readme-template', data: data }"); - $("#import-readme-container").append(readMeContainerElement); - ko.applyBindings({ data: model }, readMeContainerElement); + displayReadMeEditMarkdown(); + var fileName = window.nuget.getFileName($('#ReadMeFileInput').val()); - $("#readme-collapser-container").removeClass("hidden"); + if (fileName.length > 0) { + $('#ReadMeFileText').attr('value', fileName); + } + else { + $('#ReadMeFileText').attr('placeholder', 'Browse or Drop files to select a ReadMe.md file...'); + } + }); - window.nuget.configureExpanderHeading("readme-package-form"); + $("#ReadMeTextInput").on("change", function () { + clearReadMeError(); + }) - $("#ReadMeUrlInput").on("change blur", function () { - clearReadMeError(); - }); + $("#preview-readme-button").on('click', function () { + previewReadMeAsync(); + }); - $('#ReadMeFileText').on('click', function () { - $('#ReadMeFileInput').click(); - }); + if ($("#ReadMeTextInput").val() !== "") { + previewReadMeAsync(); + } - $('#ReadMeFileInput').on('change', function () { - clearReadMeError(); + $("#edit-markdown-button").on('click', function () { + clearReadMeError(); + displayReadMeEditMarkdown(); + }); + } + + function previewReadMeAsync(callback, error) { + // Request source type is generated off the ReadMe tab ids. + var readMeType = $(".readme-tabs li.active a").data("source-type") - displayReadMeEditMarkdown(); - var fileName = window.nuget.getFileName($('#ReadMeFileInput').val()); + var formData = new FormData(); + formData.append("SourceType", readMeType); - if (fileName.length > 0) { - $('#ReadMeFileText').attr('value', fileName); + if (readMeType == "written") { + var readMeWritten = $("#ReadMeTextInput").val(); + formData.append("SourceText", readMeWritten); + } + else if (readMeType == "url") { + var readMeUrl = $("#ReadMeUrlInput").val(); + formData.append("SourceUrl", readMeUrl); } - else { - $('#ReadMeFileText').attr('placeholder', 'Browse or Drop files to select a ReadMe.md file...'); + else if (readMeType == "file") { + var readMeFileInput = $("#ReadMeFileInput"); + var readMeFileName = readMeFileInput && readMeFileInput[0] ? window.nuget.getFileName(readMeFileInput.val()) : null; + var readMeFile = readMeFileName ? readMeFileInput[0].files[0] : null; + formData.append("SourceFile", readMeFile); } - }); - $("#ReadMeTextInput").on("change", function () { - clearReadMeError(); - }) + $.ajax({ + url: "/packages/manage/preview-readme", + type: "POST", + contentType: false, + processData: false, + data: window.nuget.addAjaxAntiForgeryToken(formData), + success: function (model, resultCodeString, fullResponse) { + clearReadMeError(); + displayReadMePreview(model); + }, + error: function (jqXHR, exception) { + var message = ""; + if (jqXHR.status == 400) { + try { + message = JSON.parse(jqXHR.responseText); + } catch (err) { + message = "Bad request. [400]"; + } + } + displayReadMeError(message); + } + }); + } - $("#preview-readme-button").on('click', function () { - previewReadMeAsync(); - }); + function displayReadMePreview(response) { + $("#readme-preview-contents").html(response); + $("#readme-preview").removeClass("hidden"); - if ($("#ReadMeTextInput").val() !== "") { - previewReadMeAsync(); - } + $('.readme-tabs').children().hide(); - $("#edit-markdown-button").on('click', function () { + $("#edit-markdown").removeClass("hidden"); + $("#preview-html").addClass("hidden"); clearReadMeError(); - displayReadMeEditMarkdown(); - }); -} + } -function previewReadMeAsync(callback, error) { - // Request source type is generated off the ReadMe tab ids. - var readMeType = $(".readme-tabs li.active a").data("source-type") + function displayReadMeEditMarkdown() { + $("#readme-preview-contents").html(""); + $("#readme-preview").addClass("hidden"); - var formData = new FormData(); - formData.append("SourceType", readMeType); + $('.readme-tabs').children().show(); - if (readMeType == "written") { - var readMeWritten = $("#ReadMeTextInput").val(); - formData.append("SourceText", readMeWritten); - } - else if (readMeType == "url") { - var readMeUrl = $("#ReadMeUrlInput").val(); - formData.append("SourceUrl", readMeUrl); + $("#edit-markdown").addClass("hidden"); + $("#preview-html").removeClass("hidden"); } - else if (readMeType == "file") { - var readMeFileInput = $("#ReadMeFileInput"); - var readMeFileName = readMeFileInput && readMeFileInput[0] ? window.nuget.getFileName(readMeFileInput.val()) : null; - var readMeFile = readMeFileName ? readMeFileInput[0].files[0] : null; - formData.append("SourceFile", readMeFile); + + function displayReadMeError(errorMsg) { + $("#readme-errors").removeClass("hidden"); + $("#preview-readme-button").attr("disabled", "disabled"); + $("#readme-error-content").text(errorMsg); } - $.ajax({ - url: "/packages/manage/preview-readme", - type: "POST", - contentType: false, - processData: false, - data: window.nuget.addAjaxAntiForgeryToken(formData), - success: function (model, resultCodeString, fullResponse) { - clearReadMeError(); - displayReadMePreview(model); - }, - error: function (jqXHR, exception) { - var message = ""; - if (jqXHR.status == 400) { - try { - message = JSON.parse(jqXHR.responseText); - } catch (err) { - message = "Bad request. [400]"; - } - } - displayReadMeError(message); + function clearReadMeError() { + if (!$("#readme-errors").hasClass("hidden")) { + $("#readme-errors").addClass("hidden"); + $("#readme-error-content").text(""); } - }); -} - -function displayReadMePreview(response) { - $("#readme-preview-contents").html(response); - $("#readme-preview").removeClass("hidden"); - - $('.readme-tabs').children().hide(); - - $("#edit-markdown").removeClass("hidden"); - $("#preview-html").addClass("hidden"); - clearReadMeError(); -} - -function displayReadMeEditMarkdown() { - $("#readme-preview-contents").html(""); - $("#readme-preview").addClass("hidden"); - - $('.readme-tabs').children().show(); - - $("#edit-markdown").addClass("hidden"); - $("#preview-html").removeClass("hidden"); -} - -function displayReadMeError(errorMsg) { - $("#readme-errors").removeClass("hidden"); - $("#preview-readme-button").attr("disabled", "disabled"); - $("#readme-error-content").text(errorMsg); -} - -function clearReadMeError() { - if (!$("#readme-errors").hasClass("hidden")) { - $("#readme-errors").addClass("hidden"); - $("#readme-error-content").text(""); + $("#preview-readme-button").removeAttr("disabled"); } - $("#preview-readme-button").removeAttr("disabled"); -} + + return bindReadMeData; +}()); diff --git a/src/NuGetGallery/Scripts/gallery/stats-dimensions.js b/src/NuGetGallery/Scripts/gallery/stats-dimensions.js index bd5301e4df..9722cd7b70 100644 --- a/src/NuGetGallery/Scripts/gallery/stats-dimensions.js +++ b/src/NuGetGallery/Scripts/gallery/stats-dimensions.js @@ -1,151 +1,157 @@ -var renderGraph = function (baseUrl, query, clickedId) { - var renderGraphHandler = function (rawData) { - var data = JSON.parse(JSON.stringify(rawData)); - - $("#loading-placeholder").hide(); - if (data != null) { - // Populate the data table - data['reportSize'] = data.Table != null ? data.Table.length : 0; - - data['ShownRows'] = function (allRows) { - var shownRows = []; - var index = 0; - while (shownRows.length < Math.min(6, allRows.length)) { - shownRows.push(allRows[index]); - var currRowSpan = shownRows[index].reduce(function (currMax, nextObj) { - return Math.max(currMax, nextObj != null ? nextObj.Rowspan : 0); - }, 0); - for (var i = 0; i < currRowSpan - 1; i++) { - index++; +var renderGraph = (function () { + 'use strict'; + + var renderGraph = function (baseUrl, query, clickedId) { + var renderGraphHandler = function (rawData) { + var data = JSON.parse(JSON.stringify(rawData)); + + $("#loading-placeholder").hide(); + if (data != null) { + // Populate the data table + data['reportSize'] = data.Table != null ? data.Table.length : 0; + + data['ShownRows'] = function (allRows) { + var shownRows = []; + var index = 0; + while (shownRows.length < Math.min(6, allRows.length)) { shownRows.push(allRows[index]); + var currRowSpan = shownRows[index].reduce(function (currMax, nextObj) { + return Math.max(currMax, nextObj != null ? nextObj.Rowspan : 0); + }, 0); + for (var i = 0; i < currRowSpan - 1; i++) { + index++; + shownRows.push(allRows[index]); + } + + index++; } + return shownRows; + }(data.Table != null ? data.Table : []); - index++; - } - return shownRows; - }(data.Table != null ? data.Table : []); + data['HiddenRows'] = data.Table != null ? data.Table.slice(data['ShownRows'].length) : []; - data['HiddenRows'] = data.Table != null ? data.Table.slice(data['ShownRows'].length) : []; + data['SetupHiddenRows'] = setupHiddenRows.bind(this, data['HiddenRows']); + } - data['SetupHiddenRows'] = setupHiddenRows.bind(this, data['HiddenRows']); - } + $("#report").remove(); - $("#report").remove(); + var reportContainerElement = document.createElement("div"); + $(reportContainerElement).attr("id", "report"); + $(reportContainerElement).attr("data-bind", "template: { name: 'report-template', data: report }"); + $("#report-container").append(reportContainerElement); - var reportContainerElement = document.createElement("div"); - $(reportContainerElement).attr("id", "report"); - $(reportContainerElement).attr("data-bind", "template: { name: 'report-template', data: report }"); - $("#report-container").append(reportContainerElement); + ko.applyBindings({ report: data }, reportContainerElement); + // Render the graph using the data table + packageDisplayGraphs(rawData); - ko.applyBindings({ report: data }, reportContainerElement); - // Render the graph using the data table - packageDisplayGraphs(rawData); + window.nuget.configureExpander( + "hidden-rows", + "CalculatorAddition", + "Show less", + "CalculatorSubtract", + "Show more"); - window.nuget.configureExpander( - "hidden-rows", - "CalculatorAddition", - "Show less", - "CalculatorSubtract", - "Show more"); - - // Add the click handler to the checkboxes - groupbyNavigation(baseUrl); + // Add the click handler to the checkboxes + groupbyNavigation(baseUrl); - // Set the focus to the checkbox that initiated this request - if (clickedId) { - $('#' + clickedId).focus(); - } - }; + // Set the focus to the checkbox that initiated this request + if (clickedId) { + $('#' + clickedId).focus(); + } + }; - $.ajax({ - url: baseUrl + query, - type: 'GET', - dataType: 'json', - success: renderGraphHandler, - error: function () { - renderGraphHandler(null); - $("#loading-placeholder").hide(); + $.ajax({ + url: baseUrl + query, + type: 'GET', + dataType: 'json', + success: renderGraphHandler, + error: function () { + renderGraphHandler(null); + $("#loading-placeholder").hide(); - $('#statistics-retry').click(function () { - renderGraph(baseUrl, query); - }); - } - }); -} - -var groupbyNavigation = function (baseUrl) { - $('.dimension-checkbox').click(function (event) { - var container = $("#stats-data-display").parent(); - $("#stats-data-display").remove(); - $("#loading-placeholder").show(); - var clickedId = event.target.id; - - var query = ''; - $('.dimension-checkbox').each(function (index, element) { - if (element.checked) { - if (query) { - query += '&'; - } else { - query = '?'; - } - query += 'groupby=' + element.value; + $('#statistics-retry').click(function () { + renderGraph(baseUrl, query); + }); } }); + }; - history.replaceState({}, "", query); - renderGraph(baseUrl, query, clickedId); - }); -} + var groupbyNavigation = function (baseUrl) { + $('.dimension-checkbox').click(function (event) { + var container = $("#stats-data-display").parent(); + $("#stats-data-display").remove(); + $("#loading-placeholder").show(); + var clickedId = event.target.id; + + var query = ''; + $('.dimension-checkbox').each(function (index, element) { + if (element.checked) { + if (query) { + query += '&'; + } else { + query = '?'; + } + query += 'groupby=' + element.value; + } + }); -var setupHiddenRows = function (data) { - var container = $("#hidden-rows"); - // no-op if we've already appended the hidden rows - if (container.children().length > 0) { - return; + history.replaceState({}, "", query); + renderGraph(baseUrl, query, clickedId); + }); } - var tableContainer = container.parent(); - container.remove(); - - var trArr = []; - for (var i = 0; i < data.length; i++) { - var tempTr = $(document.createElement("tr")); - var tdArr = []; - for (var j = 0; j < data[i].length; j++) { - var tempTd = $(document.createElement("td")); - var item = data[i][j]; - if (item != null) { - tempTd.attr("class", item.IsNumeric ? "statistics-number" : ""); - tempTd.attr("rowspan", item.Rowspan > 0 ? item.Rowspan : ""); - var content = null; - if (item.Uri != null) { - content = $(document.createElement("a")); - content.attr("href", item.Uri); - content.text(item.Data); - } else { - var textValue = item.IsNumeric ? parseInt(item.Data).toLocaleString() : item.Data; - content = $(document.createElement("span")); - content.attr("aria-label", textValue); - content.text(textValue); - } + var setupHiddenRows = function (data) { + var container = $("#hidden-rows"); + // no-op if we've already appended the hidden rows + if (container.children().length > 0) { + return; + } + + var tableContainer = container.parent(); + container.remove(); + + var trArr = []; + for (var i = 0; i < data.length; i++) { + var tempTr = $(document.createElement("tr")); + var tdArr = []; + for (var j = 0; j < data[i].length; j++) { + var tempTd = $(document.createElement("td")); + var item = data[i][j]; + if (item != null) { + tempTd.attr("class", item.IsNumeric ? "statistics-number" : ""); + tempTd.attr("rowspan", item.Rowspan > 0 ? item.Rowspan : ""); + var content = null; + if (item.Uri != null) { + content = $(document.createElement("a")); + content.attr("href", item.Uri); + content.text(item.Data); + } else { + var textValue = item.IsNumeric ? parseInt(item.Data).toLocaleString() : item.Data; + content = $(document.createElement("span")); + content.attr("aria-label", textValue); + content.text(textValue); + } - tempTd.append(content); - tdArr.push(tempTd); + tempTd.append(content); + tdArr.push(tempTd); + } } + tempTr.append(tdArr); + trArr.push(tempTr); } - tempTr.append(tdArr); - trArr.push(tempTr); + + container.append(trArr); + tableContainer.append(container); + + // When we remove the 'hidden-rows' element from the container above, we apparently kill all the event handlers on it. So reattach here. + window.nuget.configureExpander( + "hidden-rows", + "CalculatorAddition", + "Show less", + "CalculatorSubtract", + "Show more"); } - container.append(trArr); - tableContainer.append(container); - - // When we remove the 'hidden-rows' element from the container above, we apparently kill all the event handlers on it. So reattach here. - window.nuget.configureExpander( - "hidden-rows", - "CalculatorAddition", - "Show less", - "CalculatorSubtract", - "Show more"); -} \ No newline at end of file + return renderGraph; +}()); diff --git a/src/NuGetGallery/Scripts/gallery/stats-perpackagestatsgraphs.js b/src/NuGetGallery/Scripts/gallery/stats-perpackagestatsgraphs.js index 73c7e4202f..44f3cd7c3d 100644 --- a/src/NuGetGallery/Scripts/gallery/stats-perpackagestatsgraphs.js +++ b/src/NuGetGallery/Scripts/gallery/stats-perpackagestatsgraphs.js @@ -1,291 +1,295 @@ - -var graphData; -// This number is from trial and error and seeing what fit in the space -var axisLabelCharLimit = 19; - -var packageDisplayGraphs = function (data) { - window.graphData = data; - $("#stats-graph-svg").remove(); - switch (data.Id) { - case 'report-Version': - drawDownloadsByVersionBarChart(data); - break; - case 'report-ClientName': - drawDownloadsByClientNameBarChart(data); - break; - default: - break; - } -} - -var drawDownloadsByVersionBarChart = function (rawData) { - - // scrape data if we don't get a model - var data = GetChartData(rawData, function (item) { return false; }); - - if (data.length <= 0) { - d3.selectAll('#report-Version .statistics-data tbody tr').each(function () { - var item = { - label: d3.select(this).select(':nth-child(1)').text().replace(/(^\s*)|(\s*$)/g, ''), - downloads: +(d3.select(this).select(':nth-child(2)').text().replace(/[^0-9]+/g, '')) - }; - data[data.length] = item; - }); - } - - // we get descending order from server. Reverse so we can cut the right versions. - data.reverse(); - - if (data.length < 1) { - return; - } - - // limit the bar graph to the most recent 15 versions - if (data.length > 15) { - data = data.slice(data.length - 15, data.length); - } - - // draw graph - var reportGraphWidth = $('#statistics-graph-id').width(); - - reportGraphWidth = Math.min(reportGraphWidth, 1170); - - var margin = { top: 40, right: 10, bottom: 130, left: 45 }, - width = reportGraphWidth - margin.left - margin.right, - height = 450 - margin.top - margin.bottom; - - var xScale = d3.scale.ordinal() - .rangeRoundBands([10, width], .1); - - var yScale = d3.scale.linear() - .range([height, 0]); - - var xAxis = d3.svg.axis() - .scale(xScale) - .orient('bottom') - .tickFormat(function (d) { - return d.substring(0, axisLabelCharLimit) + (d.length > axisLabelCharLimit? "..." :""); - }); - - var yAxis = d3.svg.axis() - .scale(yScale) - .orient('left') - .tickFormat(function (d) { - return GetShortNumberString(d); - }); - - var svg = d3.select('#statistics-graph-id') - .append('svg') - .attr('id', 'stats-graph-svg') - .attr('width', width + margin.left + margin.right) - .attr('height', height + margin.top + margin.bottom); - - svg.append('title').text('Downloads By Version'); - svg.append('desc').text('This is a graph showing the number of downloads of this Package broken out by version.'); - - svg = svg.append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - xScale.domain(data.map(function (d) { return d.label; })); - yScale.domain([0, d3.max(data, function (d) { return d.downloads; })]); - - // the use of dx attribute on the text element is correct, however, the negative shift doesn't appear to work on Firefox - // the workaround employed here is to add a translation to the rotation transform - - svg.append("g") - .attr("class", "x axis long") - .attr("transform", "translate(0," + height + ")") - .call(xAxis) - .selectAll("text") - .style("text-anchor", "end") - //.attr("dx", "-.8em") - .attr("dy", ".15em") - .attr("transform", function (d) { - return "rotate(-65),translate(-10,0)"; - }); - - svg.selectAll(".bar") - .data(data) - .enter() - .append("rect") - .attr("class", "bar") - .attr("x", function (d) { return xScale(d.label); }) - .attr("width", xScale.rangeBand()) - .attr("y", function (d) { return yScale(d.downloads); }) - .attr("height", function (d) { return height - yScale(d.downloads); }) - .append("title").text(function (d) { return d.downloads + " Downloads"; }); - - svg.append("foreignObject") - .attr("x", "1.71em") - .attr("y", -10) - .attr("width", width - 20 + "px") - .attr("height", "2em") - .attr("font-weight", "bold") - .append("xhtml:body") - .append("p") - .attr("style", "text-align:center") - .text("Downloads for 15 Latest Package Versions (Last 6 weeks)"); - - svg.append("g") - .attr("class", "y axis") - .call(yAxis) - .append("text") - .attr("transform", "rotate(-90)") - .attr("y", 6) - .attr("dy", ".71em") - .style("text-anchor", "end") - .text("Downloads"); -} - -var drawDownloadsByClientNameBarChart = function (rawData) { - - // scrape data - - var data = GetChartData(rawData, function (item) { - return item.label === '(unknown)'; - }); - - if (data.length <= 0) { - d3.selectAll('#report-ClientName .statistics-data tbody tr').each(function () { - var item = { - label: d3.select(this).select(':nth-child(1)').text().replace(/(^\s*)|(\s*$)/g, ''), - downloads: +(d3.select(this).select(':nth-child(2)').text().replace(/[^0-9]+/g, '')) - }; - - // filter out unknown - if (item.label !== '(unknown)') { +var packageDisplayGraphs = (function () { + 'use strict'; + + var graphData; + // This number is from trial and error and seeing what fit in the space + var axisLabelCharLimit = 19; + + var packageDisplayGraphs = function (data) { + window.graphData = data; + $("#stats-graph-svg").remove(); + switch (data.Id) { + case 'report-Version': + drawDownloadsByVersionBarChart(data); + break; + case 'report-ClientName': + drawDownloadsByClientNameBarChart(data); + break; + default: + break; + } + }; + + var drawDownloadsByVersionBarChart = function (rawData) { + + // scrape data if we don't get a model + var data = GetChartData(rawData, function (item) { return false; }); + + if (data.length <= 0) { + d3.selectAll('#report-Version .statistics-data tbody tr').each(function () { + var item = { + label: d3.select(this).select(':nth-child(1)').text().replace(/(^\s*)|(\s*$)/g, ''), + downloads: +(d3.select(this).select(':nth-child(2)').text().replace(/[^0-9]+/g, '')) + }; data[data.length] = item; - } - }); - } - - data.reverse(); - - if (data.length < 1) { - return; + }); + } + + // we get descending order from server. Reverse so we can cut the right versions. + data.reverse(); + + if (data.length < 1) { + return; + } + + // limit the bar graph to the most recent 15 versions + if (data.length > 15) { + data = data.slice(data.length - 15, data.length); + } + + // draw graph + var reportGraphWidth = $('#statistics-graph-id').width(); + + reportGraphWidth = Math.min(reportGraphWidth, 1170); + + var margin = { top: 40, right: 10, bottom: 130, left: 45 }, + width = reportGraphWidth - margin.left - margin.right, + height = 450 - margin.top - margin.bottom; + + var xScale = d3.scale.ordinal() + .rangeRoundBands([10, width], .1); + + var yScale = d3.scale.linear() + .range([height, 0]); + + var xAxis = d3.svg.axis() + .scale(xScale) + .orient('bottom') + .tickFormat(function (d) { + return d.substring(0, axisLabelCharLimit) + (d.length > axisLabelCharLimit ? "..." : ""); + }); + + var yAxis = d3.svg.axis() + .scale(yScale) + .orient('left') + .tickFormat(function (d) { + return GetShortNumberString(d); + }); + + var svg = d3.select('#statistics-graph-id') + .append('svg') + .attr('id', 'stats-graph-svg') + .attr('width', width + margin.left + margin.right) + .attr('height', height + margin.top + margin.bottom); + + svg.append('title').text('Downloads By Version'); + svg.append('desc').text('This is a graph showing the number of downloads of this Package broken out by version.'); + + svg = svg.append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + xScale.domain(data.map(function (d) { return d.label; })); + yScale.domain([0, d3.max(data, function (d) { return d.downloads; })]); + + // the use of dx attribute on the text element is correct, however, the negative shift doesn't appear to work on Firefox + // the workaround employed here is to add a translation to the rotation transform + + svg.append("g") + .attr("class", "x axis long") + .attr("transform", "translate(0," + height + ")") + .call(xAxis) + .selectAll("text") + .style("text-anchor", "end") + //.attr("dx", "-.8em") + .attr("dy", ".15em") + .attr("transform", function (d) { + return "rotate(-65),translate(-10,0)"; + }); + + svg.selectAll(".bar") + .data(data) + .enter() + .append("rect") + .attr("class", "bar") + .attr("x", function (d) { return xScale(d.label); }) + .attr("width", xScale.rangeBand()) + .attr("y", function (d) { return yScale(d.downloads); }) + .attr("height", function (d) { return height - yScale(d.downloads); }) + .append("title").text(function (d) { return d.downloads + " Downloads"; }); + + svg.append("foreignObject") + .attr("x", "1.71em") + .attr("y", -10) + .attr("width", width - 20 + "px") + .attr("height", "2em") + .attr("font-weight", "bold") + .append("xhtml:body") + .append("p") + .attr("style", "text-align:center") + .text("Downloads for 15 Latest Package Versions (Last 6 weeks)"); + + svg.append("g") + .attr("class", "y axis") + .call(yAxis) + .append("text") + .attr("transform", "rotate(-90)") + .attr("y", 6) + .attr("dy", ".71em") + .style("text-anchor", "end") + .text("Downloads"); } - // draw graph + var drawDownloadsByClientNameBarChart = function (rawData) { - var reportGraphWidth = $('#statistics-graph-id').width(); - reportGraphWidth = Math.min(reportGraphWidth, 1170); + // scrape data - var margin = { top: 40, right: 10, bottom: 50, left: 150 }, - width = reportGraphWidth - margin.left - margin.right, - height = Math.max(550, data.length * 25) - margin.top - margin.bottom; - - var xScale = d3.scale.linear() - .range([0, width - 50]); - var yScale = d3.scale.ordinal() - .rangeRoundBands([height, 20], .1); - - var xAxis = d3.svg.axis() - .scale(xScale) - .orient('bottom') - .tickFormat(function (d) { - return GetShortNumberString(d); + var data = GetChartData(rawData, function (item) { + return item.label === '(unknown)'; }); - var yAxis = d3.svg.axis() - .scale(yScale) - .orient('left') - .tickFormat(function (d) { - return d.substring(0, axisLabelCharLimit) + (d.length > axisLabelCharLimit ? "..." : ""); - }); - - var svg = d3.select('#statistics-graph-id') - .append('svg') - .attr('id', 'stats-graph-svg') - .attr('width', width + margin.left + margin.right) - .attr('height', height + margin.top + margin.bottom); - - svg.append('title').text('Downloads By Client'); - svg.append('desc').text('This is a graph showing the number of downloads of this Package broken out by client.'); - - svg = svg.append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - xScale.domain([0, d3.max(data, function (d) { return d.downloads; })]); - yScale.domain(data.map(function (d) { return d.label; })); - - // the use of dx attribute on the text element is correct, however, the negative shift doesn't appear to work on Firefox - // the workaround employed here is to add a translation to the rotation transform - - svg.append("g") - .attr("class", "x axis") - .attr("transform", "translate(0," + height + ")") - .call(xAxis); - - svg.selectAll(".bar") - .data(data) - .enter() - .append("rect") - .attr("class", "bar") - .attr("x", 0) - .attr("width", function (d) { return xScale(d.downloads); }) - .attr("y", function (d) { return yScale(d.label); }) - .attr("height", yScale.rangeBand()) - .append("title").text(function (d) { return d.downloads.toLocaleString() + " Downloads"; }); - - svg.append("foreignObject") - .attr("x", 0) - .attr("y", -10) - .attr("width", width + "px") - .attr("height", "2em") - .attr("font-weight", "bold") - .append("xhtml:body") - .append("p") - .attr("style", "text-align:center") - .text("Downloads by Client (Last 6 weeks)"); - - svg.append("g") - .attr("class", "y axis long") - .call(yAxis); -} - -var GetChartData = function (rawData, filter) { - var data = []; - - if (rawData.Table && rawData.Table.length > 0) { - rawData.Table.forEach(function (dataPoint) { - var item = { - label: dataPoint[0].Data, - downloads: window.nuget.parseNumber(dataPoint[1].Data) - }; - - if (!filter(item)) { - data[data.length] = item; - } - }); + if (data.length <= 0) { + d3.selectAll('#report-ClientName .statistics-data tbody tr').each(function () { + var item = { + label: d3.select(this).select(':nth-child(1)').text().replace(/(^\s*)|(\s*$)/g, ''), + downloads: +(d3.select(this).select(':nth-child(2)').text().replace(/[^0-9]+/g, '')) + }; + + // filter out unknown + if (item.label !== '(unknown)') { + data[data.length] = item; + } + }); + } + + data.reverse(); + + if (data.length < 1) { + return; + } + + // draw graph + + var reportGraphWidth = $('#statistics-graph-id').width(); + reportGraphWidth = Math.min(reportGraphWidth, 1170); + + var margin = { top: 40, right: 10, bottom: 50, left: 150 }, + width = reportGraphWidth - margin.left - margin.right, + height = Math.max(550, data.length * 25) - margin.top - margin.bottom; + + var xScale = d3.scale.linear() + .range([0, width - 50]); + var yScale = d3.scale.ordinal() + .rangeRoundBands([height, 20], .1); + + var xAxis = d3.svg.axis() + .scale(xScale) + .orient('bottom') + .tickFormat(function (d) { + return GetShortNumberString(d); + }); + + var yAxis = d3.svg.axis() + .scale(yScale) + .orient('left') + .tickFormat(function (d) { + return d.substring(0, axisLabelCharLimit) + (d.length > axisLabelCharLimit ? "..." : ""); + }); + + var svg = d3.select('#statistics-graph-id') + .append('svg') + .attr('id', 'stats-graph-svg') + .attr('width', width + margin.left + margin.right) + .attr('height', height + margin.top + margin.bottom); + + svg.append('title').text('Downloads By Client'); + svg.append('desc').text('This is a graph showing the number of downloads of this Package broken out by client.'); + + svg = svg.append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + xScale.domain([0, d3.max(data, function (d) { return d.downloads; })]); + yScale.domain(data.map(function (d) { return d.label; })); + + // the use of dx attribute on the text element is correct, however, the negative shift doesn't appear to work on Firefox + // the workaround employed here is to add a translation to the rotation transform + + svg.append("g") + .attr("class", "x axis") + .attr("transform", "translate(0," + height + ")") + .call(xAxis); + + svg.selectAll(".bar") + .data(data) + .enter() + .append("rect") + .attr("class", "bar") + .attr("x", 0) + .attr("width", function (d) { return xScale(d.downloads); }) + .attr("y", function (d) { return yScale(d.label); }) + .attr("height", yScale.rangeBand()) + .append("title").text(function (d) { return d.downloads.toLocaleString() + " Downloads"; }); + + svg.append("foreignObject") + .attr("x", 0) + .attr("y", -10) + .attr("width", width + "px") + .attr("height", "2em") + .attr("font-weight", "bold") + .append("xhtml:body") + .append("p") + .attr("style", "text-align:center") + .text("Downloads by Client (Last 6 weeks)"); + + svg.append("g") + .attr("class", "y axis long") + .call(yAxis); } - return data; -} + var GetChartData = function (rawData, filter) { + var data = []; -var GetShortNumberString = function (number) { - if (number == 0) { - return "0"; - } + if (rawData.Table && rawData.Table.length > 0) { + rawData.Table.forEach(function (dataPoint) { + var item = { + label: dataPoint[0].Data, + downloads: window.nuget.parseNumber(dataPoint[1].Data) + }; - var abbreviation = ["", "k", "M", "B", "T", "q", "Q", "s", "S", "o", "n"]; - var numDiv = 0; - while (number >= 1000) { - number = number / 1000.0; - numDiv++; - } + if (!filter(item)) { + data[data.length] = item; + } + }); + } - rounded = Math.floor(number); - if (rounded >= 100) { - number = number.toPrecision(3); - } else { - number = number.toPrecision(2); + return data; } - if (numDiv >= abbreviation.Length) { - return number + "10^" + numDiv*3; + var GetShortNumberString = function (number) { + if (number == 0) { + return "0"; + } + + var abbreviation = ["", "k", "M", "B", "T", "q", "Q", "s", "S", "o", "n"]; + var numDiv = 0; + while (number >= 1000) { + number = number / 1000.0; + numDiv++; + } + + var rounded = Math.floor(number); + if (rounded >= 100) { + number = number.toPrecision(3); + } else { + number = number.toPrecision(2); + } + + if (numDiv >= abbreviation.Length) { + return number + "10^" + numDiv * 3; + } + return number + abbreviation[numDiv]; } - return number + abbreviation[numDiv]; -} + $(window).resize(function () { + packageDisplayGraphs(graphData); + }); -$(window).resize(function () { - packageDisplayGraphs(graphData); -}); \ No newline at end of file + return packageDisplayGraphs; +}()); diff --git a/src/NuGetGallery/Scripts/nugetgallery.js b/src/NuGetGallery/Scripts/nugetgallery.js index 8560a05709..c703aaca4f 100644 --- a/src/NuGetGallery/Scripts/nugetgallery.js +++ b/src/NuGetGallery/Scripts/nugetgallery.js @@ -1,14 +1,20 @@ // Global utility script for NuGetGallery /// -// Shared function for adding an anti-forgery token defined by ViewHelpers.AjaxAntiForgeryToken to an ajax request -function addAjaxAntiForgeryToken(data) { - var $field = $("#AntiForgeryForm input[name=__RequestVerificationToken]"); - data["__RequestVerificationToken"] = $field.val(); - return data; -} +var addAjaxAntiForgeryToken = (function () { + 'use strict'; + + // Shared function for adding an anti-forgery token defined by ViewHelpers.AjaxAntiForgeryToken to an ajax request + return function (data) { + var $field = $("#AntiForgeryForm input[name=__RequestVerificationToken]"); + data["__RequestVerificationToken"] = $field.val(); + return data; + }; +}()); (function (window, $, undefined) { + 'use strict'; + $(function () { // Export an object with global config data var app = $(document.documentElement).data(); diff --git a/src/NuGetGallery/Scripts/stats.js b/src/NuGetGallery/Scripts/stats.js deleted file mode 100644 index 7b206d0d56..0000000000 --- a/src/NuGetGallery/Scripts/stats.js +++ /dev/null @@ -1,66 +0,0 @@ -function getStats(currData) { - currData = currData || {}; - - $.get(window.app.root + 'stats/totals', function (data) { - var section = $('section.aggstats'); - section.show(); - update(data, currData, 'UniquePackages'); - update(data, currData, 'Downloads'); - update(data, currData, 'TotalPackages'); - }).error(function () { - // Don't show the stats error anymore. Just fail silently. - // var section = $('section.aggstatserr'); - // section.show(); - }); -} - -function update(data, currData, key) { - var currentValue = currData[key] || ''; - var value = data[key].toString(); - var self = $('#' + key); - - if (currentValue != value) { - currData[key] = value; - var length = value.length; - var currLength = currentValue.length; - var items = self.children('span'); - - if (currLength > length) { - items.slice(0, currLength - length).remove(); - items = items.slice(currLength - length); - } - - if (currLength) { - // Do not animate the first time around. - $.each(value.split('').reverse(), function (i, e) { - var c = (i <= currLength) ? currentValue.charAt(currLength - i - 1) : ''; - if (c != e) { - var el = $(items[length - i - 1]); - animateEl(el, e); - } - }); - } - if (currLength < length) { - var i; - for (i = currLength; i < length; i++) { - self.prepend('' + value.charAt(length - i - 1) + ''); - } - items = self.children('span'); - } - } -} - -function animateEl(el, v) { - v = v || ''; - var parent = el.parent(); - el.stop(true, true).animate({ top: 0.3 * parseInt(parent.height()) }, 350, 'linear', function () { - $(this).html(v).css({ top: -0.8 * parseInt(parent.height()) }).animate({ top: 0 }, 350, 'linear'); - }); -} - -$(document).ready(function () { - var elem = document.getElementsByClassName("aggstats"); - if (elem != null && elem.length > 0) { - getStats(); - } -}); diff --git a/src/NuGetGallery/Scripts/statsgraphs.js b/src/NuGetGallery/Scripts/statsgraphs.js deleted file mode 100644 index 719210ddd9..0000000000 --- a/src/NuGetGallery/Scripts/statsgraphs.js +++ /dev/null @@ -1,165 +0,0 @@ -var drawNugetClientVersionBarChart = function () { - - var margin = { top: 20, right: 30, bottom: 80, left: 80 }, - width = 460 - margin.left - margin.right, - height = 320 - margin.top - margin.bottom; - - var xScale = d3.scale.ordinal() - .rangeRoundBands([0, width], .1); - - var yScale = d3.scale.linear() - .range([height, 0]); - - var xAxis = d3.svg.axis() - .scale(xScale) - .orient('bottom'); - - var yAxis = d3.svg.axis() - .scale(yScale) - .orient('left'); - - var svg = d3.select('#downloads-by-nuget-version').append('svg') - .attr('width', width + margin.left + margin.right) - .attr('height', height + margin.top + margin.bottom); - - svg.append('title').text('NuGet Client Usage (Last 6 Weeks)'); - svg.append('desc').text('This is a graph showing the number of downloads by each version of the NuGet client over the last six weeks.'); - - svg = svg.append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - var data = []; - - d3.selectAll('#downloads-by-nuget-version tbody tr').each(function () { - var item = { - nugetVersion: d3.select(this).select(':nth-child(1)').text(), - downloads: +(d3.select(this).select(':nth-child(2)').text().replace(/[^0-9]+/g, '')), - percentage: d3.select(this).select(':nth-child(3)').text(), - }; - data[data.length] = item; - }); - - xScale.domain(data.map(function (d) { return d.nugetVersion; })); - yScale.domain([0, d3.max(data, function (d) { return d.downloads; })]); - - // the use of dx attribute on the text element is correct, however, the negative shift doesn't appear to work on Firefox - // the workaround employed here is to add a translation to the rotation transform - - svg.append("g") - .attr("class", "x axis") - .attr("transform", "translate(0," + height + ")") - .call(xAxis) - .selectAll("text") - .style("text-anchor", "end") - //.attr("dx", "-.8em") - .attr("dy", ".15em") - .attr("transform", function (d) { - return "rotate(-65),translate(-10,0)" - }); - - svg.append("g") - .attr("class", "y axis") - .call(yAxis) - .append("text") - .attr("transform", "rotate(-90)") - .attr("y", 6) - .attr("dy", ".71em") - .style("text-anchor", "end") - .text("Downloads"); - - svg.selectAll(".bar") - .data(data) - .enter() - .append("rect") - .attr("class", "bar") - .attr("x", function (d) { return xScale(d.nugetVersion); }) - .attr("width", xScale.rangeBand()) - .attr("y", function (d) { return yScale(d.downloads); }) - .attr("height", function (d) { return height - yScale(d.downloads); }) - .append("title") - .text(function (d) { return d.percentage; }); -} - -var drawMonthlyDownloadsLineChart = function () { - - var margin = { top: 20, right: 20, bottom: 80, left: 80 }, - width = 400 - margin.left - margin.right, - height = 300 - margin.top - margin.bottom; - - var xScale = d3.scale.ordinal() - .rangePoints([0, width]); - - var yScale = d3.scale.linear() - .range([height, 0]); - - var xAxis = d3.svg.axis() - .scale(xScale) - .orient('bottom'); - - var yAxis = d3.svg.axis() - .scale(yScale) - .orient('left'); - - var data = []; - - d3.selectAll('#downloads-per-month tbody tr').each(function () { - var item = { - month: d3.select(this).select(':nth-child(1)').text(), - downloads: +(d3.select(this).select(':nth-child(2)').text().replace(/[^0-9]+/g, '')) - }; - data[data.length] = item; - }); - - var line = d3.svg.line() - .x(function (d) { return xScale(d.month); }) - .y(function (d) { return yScale(d.downloads); }); - - var svg = d3.select("#downloads-per-month").append("svg") - .attr("width", width + margin.left + margin.right) - .attr("height", height + margin.top + margin.bottom); - - svg.append('title').text('Packages Downloaded - Month to Date'); - svg.append('desc').text('This is a graph showing the number of downloads from NuGet per month.'); - - svg = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")"); - - xScale.domain(data.map(function (d) { return d.month; })); - yScale.domain([0, d3.max(data, function (d) { return d.downloads; })]); - - // the use of dx attribute on the text element is correct, however, the negative shift doesn't appear to work on Firefox - // the workaround employed here is to add a translation to the rotation transform - - svg.append("g") - .attr("class", "x axis") - .attr("transform", "translate(0," + height + ")") - .call(xAxis) - .selectAll("text") - .style("text-anchor", "end") - //.attr("dx", "-.8em") - .attr("dy", ".15em") - .attr("transform", function (d) { - return "rotate(-65),translate(-10,0)" - }); - - svg.append("g") - .attr("class", "y axis") - .call(yAxis); - - svg.append("path") - .datum(data) - .attr("class", "line") - .attr("d", line); - - var formatDownloads = d3.format(','); - - svg.selectAll('.point') - .data(data) - .enter() - .append("svg:circle") - .attr("class", "line-graph-dot") - .attr("cx", function (d) { return xScale(d.month); }) - .attr("cy", function (d) { return yScale(d.downloads); }) - .attr("r", 5) - .append("title") - .text(function (d) { return formatDownloads(d.downloads); }); -} - diff --git a/src/NuGetGallery/Scripts/supportrequests.js b/src/NuGetGallery/Scripts/supportrequests.js index daa4571150..2c48b21bda 100644 --- a/src/NuGetGallery/Scripts/supportrequests.js +++ b/src/NuGetGallery/Scripts/supportrequests.js @@ -1,277 +1,294 @@ -function HistoryViewModel() { - var $self = this; - - this.issue = ko.observable(); - this.historyEntries = ko.observableArray(); -}; - -function EditViewModel(editUrl) { - var $self = this; - - this.issue = ko.observable(); - this.editAssignedToId = ko.observable(); - this.editIssueStatusId = ko.observable(); - this.editIssueComment = ko.observable(); - this.assignedToChoices = ko.observableArray(); - this.issueStatusChoices = ko.observableArray(); - - this.updateSupportRequest = function (success, error) { - var model = { - issueKey: $self.issue.Key, - assignedToId: $self.editAssignedToId, - issueStatusId: $self.editIssueStatusId, - comment: $self.editIssueComment() - }; - - $.ajax({ - url: editUrl, - type: 'POST', - cache: false, - dataType: 'json', - data: addAjaxAntiForgeryToken(model), - success: success - }) - .error(error); - } -} - -function SupportRequestsViewModel(editUrl, filterUrl, historyUrl) { - var $self = this; - - this.editUrl = editUrl; - this.filterUrl = filterUrl; - this.historyUrl = historyUrl; - this.editSupportRequestForm = $('#editSupportRequest-form').get(0); - this.editAssignedToCtrl = $('#editAssignedTo').get(0); - this.editIssueStatusCtrl = $('#editIssueStatus').get(0); - this.editIssueCommentCtrl = $('#editIssueComment').get(0); - this.historyTableCtrl = $('#history-table').get(0); - - this.assignedToFilter = ko.observable(); - this.issueStatusIdFilter = ko.observable(); - this.reasonFilter = ko.observable(); - this.pageNumber = ko.observable(1); - this.maxPageNumber = ko.observable(1); - this.take = ko.observable(30); - - this.hasPreviousPage = ko.computed(function () { - return $self.pageNumber() > 1; - }); +var HistoryViewModel = (function () { + 'use strict'; - this.hasNextPage = ko.computed(function () { - return $self.pageNumber() < $self.maxPageNumber(); - }); + return function () { + var $self = this; - this.goToPreviousPage = function () { - $self.filter($self.pageNumber() - 1, $self.take()); + this.issue = ko.observable(); + this.historyEntries = ko.observableArray(); }; - this.goToNextPage = function () { - $self.filter($self.pageNumber() + 1, $self.take()); +}()); + +var EditViewModel = (function () { + 'use strict'; + + return function (editUrl) { + var $self = this; + + this.issue = ko.observable(); + this.editAssignedToId = ko.observable(); + this.editIssueStatusId = ko.observable(); + this.editIssueComment = ko.observable(); + this.assignedToChoices = ko.observableArray(); + this.issueStatusChoices = ko.observableArray(); + + this.updateSupportRequest = function (success, error) { + var model = { + issueKey: $self.issue.Key, + assignedToId: $self.editAssignedToId, + issueStatusId: $self.editIssueStatusId, + comment: $self.editIssueComment() + }; + + $.ajax({ + url: editUrl, + type: 'POST', + cache: false, + dataType: 'json', + data: addAjaxAntiForgeryToken(model), + success: success + }) + .error(error); + } }; - - this.filteredIssues = ko.observableArray(); - this.assignedToChoices = ko.observableArray(); - this.issueStatusChoices = ko.observableArray(); - this.reasonChoices = ko.observableArray(); - - this.styleButtons = function () { - $('a.editButton').button( - { - icons: { - primary: 'ui-icon-pencil' - } +}()); + +var SupportRequestsViewModel = (function () { + 'use strict'; + + return function (editUrl, filterUrl, historyUrl) { + var $self = this; + + this.editUrl = editUrl; + this.filterUrl = filterUrl; + this.historyUrl = historyUrl; + this.editSupportRequestForm = $('#editSupportRequest-form').get(0); + this.editAssignedToCtrl = $('#editAssignedTo').get(0); + this.editIssueStatusCtrl = $('#editIssueStatus').get(0); + this.editIssueCommentCtrl = $('#editIssueComment').get(0); + this.historyTableCtrl = $('#history-table').get(0); + + this.assignedToFilter = ko.observable(); + this.issueStatusIdFilter = ko.observable(); + this.reasonFilter = ko.observable(); + this.pageNumber = ko.observable(1); + this.maxPageNumber = ko.observable(1); + this.take = ko.observable(30); + + this.hasPreviousPage = ko.computed(function () { + return $self.pageNumber() > 1; }); - $('a.historyButton').button( - { - icons: { - primary: 'ui-icon-clock' - } - }); - $('a.contactButton').button( - { - icons: { - primary: 'ui-icon-mail-closed' - } + + this.hasNextPage = ko.computed(function () { + return $self.pageNumber() < $self.maxPageNumber(); }); - } - this.updateSupportRequest = function () { - var updatedViewModel = ko.dataFor($self.editSupportRequestForm); + this.goToPreviousPage = function () { + $self.filter($self.pageNumber() - 1, $self.take()); + }; + this.goToNextPage = function () { + $self.filter($self.pageNumber() + 1, $self.take()); + }; - updatedViewModel.updateSupportRequest( - function () { - $self.pageNumber(0); - $self.filter(); - $self.editSupportRequestDialog.dialog("close"); - }, - function (jqXhr, textStatus, errorThrown) { - alert("Error: " + errorThrown); - }); - } - - this.historyDialog = $('#history-dialog').dialog({ - autoOpen: false, - modal: true, - width: 800, - overlay: { - backgroundColor: '#000', - opacity: 0.5 - }, - buttons: { - "Close": function () { - $self.historyDialog.dialog("close"); - } + this.filteredIssues = ko.observableArray(); + this.assignedToChoices = ko.observableArray(); + this.issueStatusChoices = ko.observableArray(); + this.reasonChoices = ko.observableArray(); + + this.styleButtons = function () { + $('a.editButton').button( + { + icons: { + primary: 'ui-icon-pencil' + } + }); + $('a.historyButton').button( + { + icons: { + primary: 'ui-icon-clock' + } + }); + $('a.contactButton').button( + { + icons: { + primary: 'ui-icon-mail-closed' + } + }); } - }); - this.editSupportRequestFields = $([]) - .add($self.editAssignedToCtrl) - .add($self.editIssueStatusCtrl) - .add($self.editIssueCommentCtrl); - - this.editSupportRequestDialog = $("#editSupportRequest-dialog").dialog({ - autoOpen: false, - modal: true, - width: 400, - overlay: { - backgroundColor: '#000', - opacity: 0.5 - }, - buttons: { - "Save Changes": $self.updateSupportRequest, - Cancel: function () { - $self.editSupportRequestDialog.dialog("close"); - } - }, - close: function () { - $('#editSupportRequest-form')[0].reset(); - $self.editSupportRequestFields.removeClass("ui-state-error"); + this.updateSupportRequest = function () { + var updatedViewModel = ko.dataFor($self.editSupportRequestForm); + + updatedViewModel.updateSupportRequest( + function () { + $self.pageNumber(0); + $self.filter(); + $self.editSupportRequestDialog.dialog("close"); + }, + function (jqXhr, textStatus, errorThrown) { + alert("Error: " + errorThrown); + }); } - }); - this.editSupportRequest = function (supportRequestViewModel) { - - var editViewModel = new EditViewModel($self.editUrl); - editViewModel.issue = supportRequestViewModel; - editViewModel.assignedToChoices = $self.assignedToChoices; - editViewModel.issueStatusChoices = $self.issueStatusChoices.filter(function (value) { - return value.Text !== 'Unresolved'; + this.historyDialog = $('#history-dialog').dialog({ + autoOpen: false, + modal: true, + width: 800, + overlay: { + backgroundColor: '#000', + opacity: 0.5 + }, + buttons: { + "Close": function () { + $self.historyDialog.dialog("close"); + } + } }); - editViewModel.editAssignedToId = supportRequestViewModel.AssignedTo; - editViewModel.editIssueStatusId = supportRequestViewModel.IssueStatusId; + this.editSupportRequestFields = $([]) + .add($self.editAssignedToCtrl) + .add($self.editIssueStatusCtrl) + .add($self.editIssueCommentCtrl); + + this.editSupportRequestDialog = $("#editSupportRequest-dialog").dialog({ + autoOpen: false, + modal: true, + width: 400, + overlay: { + backgroundColor: '#000', + opacity: 0.5 + }, + buttons: { + "Save Changes": $self.updateSupportRequest, + Cancel: function () { + $self.editSupportRequestDialog.dialog("close"); + } + }, + close: function () { + $('#editSupportRequest-form')[0].reset(); + $self.editSupportRequestFields.removeClass("ui-state-error"); + } + }); - ko.applyBindings(editViewModel, $self.editSupportRequestForm); + this.editSupportRequest = function (supportRequestViewModel) { - $self.editSupportRequestDialog.dialog('option', 'title', 'Edit SR-' + supportRequestViewModel.Key); - $self.editSupportRequestDialog.dialog('open'); - return false; - }; + var editViewModel = new EditViewModel($self.editUrl); + editViewModel.issue = supportRequestViewModel; + editViewModel.assignedToChoices = $self.assignedToChoices; + editViewModel.issueStatusChoices = $self.issueStatusChoices.filter(function (value) { + return value.Text !== 'Unresolved'; + }); - this.generateContactUserUrl = function (supportRequestViewModel) { - return 'mailto:' + supportRequestViewModel.OwnerEmail - + '?subject=[NuGet.org Support] ' + supportRequestViewModel.IssueTitle - + '&CC=support@nuget.org'; - }; + editViewModel.editAssignedToId = supportRequestViewModel.AssignedTo; + editViewModel.editIssueStatusId = supportRequestViewModel.IssueStatusId; - this.showHistory = function (supportRequestViewModel) { + ko.applyBindings(editViewModel, $self.editSupportRequestForm); - var url = $self.generateHistoryUrl(supportRequestViewModel); + $self.editSupportRequestDialog.dialog('option', 'title', 'Edit SR-' + supportRequestViewModel.Key); + $self.editSupportRequestDialog.dialog('open'); + return false; + }; - $.ajax({ - url: url, - type: 'GET', - cache: false, - dataType: 'json', - success: function (data) { + this.generateContactUserUrl = function (supportRequestViewModel) { + return 'mailto:' + supportRequestViewModel.OwnerEmail + + '?subject=[NuGet.org Support] ' + supportRequestViewModel.IssueTitle + + '&CC=support@nuget.org'; + }; - var historyViewModel = ko.dataFor($self.historyTableCtrl); - historyViewModel.issue(supportRequestViewModel); - historyViewModel.historyEntries(data); + this.showHistory = function (supportRequestViewModel) { - $self.historyDialog.dialog('option', 'title', 'History for SR-' + supportRequestViewModel.Key); - $self.historyDialog.dialog('open'); - } - }) - .error(function (jqXhr, textStatus, errorThrown) { - alert("Error: " + errorThrown); - }); + var url = $self.generateHistoryUrl(supportRequestViewModel); - return false; - }; + $.ajax({ + url: url, + type: 'GET', + cache: false, + dataType: 'json', + success: function (data) { - this.generateUserProfileUrl = function (supportRequestViewModel) { - if (supportRequestViewModel.CreatedBy.toUpperCase !== 'ANONYMOUS') { - return supportRequestViewModel.SiteRoot + 'Profiles/' + supportRequestViewModel.CreatedBy; - } - return '#'; - } + var historyViewModel = ko.dataFor($self.historyTableCtrl); + historyViewModel.issue(supportRequestViewModel); + historyViewModel.historyEntries(data); - this.generatePackageDetailsUrl = function(supportRequestViewModel) { - return supportRequestViewModel.SiteRoot + 'packages/' + supportRequestViewModel.PackageId + '/' + supportRequestViewModel.PackageVersion; - } + $self.historyDialog.dialog('option', 'title', 'History for SR-' + supportRequestViewModel.Key); + $self.historyDialog.dialog('open'); + } + }) + .error(function (jqXhr, textStatus, errorThrown) { + alert("Error: " + errorThrown); + }); - this.generateHistoryUrl = function (supportRequestViewModel) { - return $self.historyUrl + '?id=' + supportRequestViewModel.Key; - } + return false; + }; - this.getStyleForIssueStatus = function (supportRequestViewModel) { - if (supportRequestViewModel.IssueStatusName.toUpperCase() === 'NEW') { - return 'color: #FF1F19; style: bold;'; + this.generateUserProfileUrl = function (supportRequestViewModel) { + if (supportRequestViewModel.CreatedBy.toUpperCase !== 'ANONYMOUS') { + return supportRequestViewModel.SiteRoot + 'Profiles/' + supportRequestViewModel.CreatedBy; + } + return '#'; } - else if (supportRequestViewModel.IssueStatusName.toUpperCase() === 'RESOLVED') { - return 'color: #09B25B; style: bold;'; + + this.generatePackageDetailsUrl = function (supportRequestViewModel) { + return supportRequestViewModel.SiteRoot + 'packages/' + supportRequestViewModel.PackageId + '/' + supportRequestViewModel.PackageVersion; } - else { - return 'color: #FF8D00; style: bold;'; + + this.generateHistoryUrl = function (supportRequestViewModel) { + return $self.historyUrl + '?id=' + supportRequestViewModel.Key; } - } - this.applyFilter = function () { - $self.filter($self.pageNumber(), $self.take()); - }; + this.getStyleForIssueStatus = function (supportRequestViewModel) { + if (supportRequestViewModel.IssueStatusName.toUpperCase() === 'NEW') { + return 'color: #FF1F19; style: bold;'; + } + else if (supportRequestViewModel.IssueStatusName.toUpperCase() === 'RESOLVED') { + return 'color: #09B25B; style: bold;'; + } + else { + return 'color: #FF8D00; style: bold;'; + } + } - this.filter = function (pageNumber, take) { + this.applyFilter = function () { + $self.filter($self.pageNumber(), $self.take()); + }; - var url = $self.filterUrl + '?pageNumber=' + pageNumber + '&take=' + take; + this.filter = function (pageNumber, take) { - if ($self.assignedToFilter() !== undefined) { - url += '&assignedToId=' + $self.assignedToFilter(); - } + var url = $self.filterUrl + '?pageNumber=' + pageNumber + '&take=' + take; - if ($self.reasonFilter() !== undefined && $self.reasonFilter() !== '') { - url += '&reason=' + $self.reasonFilter(); - } + if ($self.assignedToFilter() !== undefined) { + url += '&assignedToId=' + $self.assignedToFilter(); + } - if ($self.issueStatusIdFilter() !== undefined) { - url += '&issueStatusId=' + $self.issueStatusIdFilter(); - } + if ($self.reasonFilter() !== undefined && $self.reasonFilter() !== '') { + url += '&reason=' + $self.reasonFilter(); + } - $.ajax({ - url: url, - type: 'GET', - cache: false, - dataType: 'json', - success: function (data) { - var parsed = JSON.parse(data); - $self.filteredIssues(parsed.Issues); - $self.pageNumber(parsed.CurrentPageNumber); - $self.maxPageNumber(parsed.MaxPage); - $self.styleButtons(); + if ($self.issueStatusIdFilter() !== undefined) { + url += '&issueStatusId=' + $self.issueStatusIdFilter(); } - }) - .error(function (jqXhr, textStatus, errorThrown) { - alert("Error: " + errorThrown); - }); - }; -}; - -$(function () { - ko.bindingHandlers.datetime = { - update: function (element, valueAccessor) { - var value = valueAccessor(); - var date = moment(value); - $(element).text(date.format("L") + " " + date.format("LTS")); - } + + $.ajax({ + url: url, + type: 'GET', + cache: false, + dataType: 'json', + success: function (data) { + var parsed = JSON.parse(data); + $self.filteredIssues(parsed.Issues); + $self.pageNumber(parsed.CurrentPageNumber); + $self.maxPageNumber(parsed.MaxPage); + $self.styleButtons(); + } + }) + .error(function (jqXhr, textStatus, errorThrown) { + alert("Error: " + errorThrown); + }); + }; }; -}); \ No newline at end of file + +}()); + +(function () { + 'use strict'; + + $(function () { + ko.bindingHandlers.datetime = { + update: function (element, valueAccessor) { + var value = valueAccessor(); + var date = moment(value); + $(element).text(date.format("L") + " " + date.format("LTS")); + } + }; + }); +}()); From 3c5a43c9ae205f8fb3352cd54a8ca2336df5bb95 Mon Sep 17 00:00:00 2001 From: Nikolche Kolev Date: Wed, 11 Apr 2018 12:47:31 -0700 Subject: [PATCH 02/32] Add alternate install command for global tools packages on the packages page (#5684) Don't display the tabs for managers that are not global tools compatible. --- .../ViewModels/PackageManagerViewModel.cs | 17 ++++ .../ViewModels/PackageViewModel.cs | 2 + .../ThirdPartyPackageManagerViewModel.cs | 7 ++ .../Views/Packages/DisplayPackage.cshtml | 88 +++++++++++++------ 4 files changed, 85 insertions(+), 29 deletions(-) diff --git a/src/NuGetGallery/ViewModels/PackageManagerViewModel.cs b/src/NuGetGallery/ViewModels/PackageManagerViewModel.cs index 5c78a6073f..7905f8f2cb 100644 --- a/src/NuGetGallery/ViewModels/PackageManagerViewModel.cs +++ b/src/NuGetGallery/ViewModels/PackageManagerViewModel.cs @@ -27,5 +27,22 @@ public class PackageManagerViewModel /// A string that represents the command used to install a specific package. /// public string InstallPackageCommand { get; set; } + + /// + /// The alert message that contains clarifications about the command/scenario + /// + public string AlertMessage { get; set; } + + /// + /// The level with which the above message will be displayed. + /// + public AlertLevel AlertLevel { get; set; } + } + + public enum AlertLevel + { + None, + Info, + Warning } } \ No newline at end of file diff --git a/src/NuGetGallery/ViewModels/PackageViewModel.cs b/src/NuGetGallery/ViewModels/PackageViewModel.cs index 02c9b8cd6c..b417cb0b0c 100644 --- a/src/NuGetGallery/ViewModels/PackageViewModel.cs +++ b/src/NuGetGallery/ViewModels/PackageViewModel.cs @@ -43,6 +43,7 @@ public PackageViewModel(Package package) LatestStableVersionSemVer2 = package.IsLatestStableSemVer2; LastUpdated = package.Published; Listed = package.Listed; + IsDotnetToolPackageType = package.PackageTypes.Any(e => e.Name.Equals("DotnetTool", StringComparison.OrdinalIgnoreCase)); _packageStatus = package.PackageStatusKey; DownloadCount = package.DownloadCount; Prerelease = package.IsPrerelease; @@ -71,6 +72,7 @@ public PackageViewModel(Package package) public bool Prerelease { get; set; } public int DownloadCount { get; set; } public bool Listed { get; set; } + public bool IsDotnetToolPackageType { get; set; } public bool FailedValidation => _packageStatus == PackageStatus.FailedValidation; public bool Available => _packageStatus == PackageStatus.Available; public bool Validating => _packageStatus == PackageStatus.Validating; diff --git a/src/NuGetGallery/ViewModels/ThirdPartyPackageManagerViewModel.cs b/src/NuGetGallery/ViewModels/ThirdPartyPackageManagerViewModel.cs index ae51115852..befeecfce8 100644 --- a/src/NuGetGallery/ViewModels/ThirdPartyPackageManagerViewModel.cs +++ b/src/NuGetGallery/ViewModels/ThirdPartyPackageManagerViewModel.cs @@ -14,5 +14,12 @@ public class ThirdPartyPackageManagerViewModel : PackageManagerViewModel /// for support. /// public string ContactUrl { get; set; } + + public ThirdPartyPackageManagerViewModel(string contactUrl) + { + ContactUrl = contactUrl; + AlertLevel = AlertLevel.Warning; + AlertMessage = string.Format("The NuGet Team does not provide support for this client. Please contact its maintainers for support.", contactUrl); + } } } \ No newline at end of file diff --git a/src/NuGetGallery/Views/Packages/DisplayPackage.cshtml b/src/NuGetGallery/Views/Packages/DisplayPackage.cshtml index f5cfc46f37..5409e8738d 100644 --- a/src/NuGetGallery/Views/Packages/DisplayPackage.cshtml +++ b/src/NuGetGallery/Views/Packages/DisplayPackage.cshtml @@ -6,34 +6,54 @@ var absolutePackageUrl = Url.Absolute(Url.Package(Model.Id)); - var packageManagers = new PackageManagerViewModel[] + PackageManagerViewModel[] packageManagers; + + if (Model.IsDotnetToolPackageType) { - new PackageManagerViewModel() + packageManagers = new PackageManagerViewModel[] { - Id = "package-manager", - Name = "Package Manager", - CommandPrefix = "PM> ", - InstallPackageCommand = string.Format("Install-Package {0} -Version {1}", Model.Id, Model.Version) - }, - - new PackageManagerViewModel() + new PackageManagerViewModel() + { + Id = "dotnet-cli", + Name = ".NET CLI", + CommandPrefix = "> ", + InstallPackageCommand = string.Format("dotnet tool install --global {0} --version {1}", Model.Id, Model.Version), + AlertLevel = AlertLevel.Info, + AlertMessage = string.Format("This package contains a .NET Core Global Tool you can call from the shell/command line.", "https://aka.ms/global-tools"), + } + }; + } + else + { + packageManagers = new PackageManagerViewModel[] { - Id = "dotnet-cli", - Name = ".NET CLI", - CommandPrefix = "> ", - InstallPackageCommand = string.Format("dotnet add package {0} --version {1}", Model.Id, Model.Version) - }, + new PackageManagerViewModel() + { + Id = "package-manager", + Name = "Package Manager", + CommandPrefix = "PM> ", + InstallPackageCommand = string.Format("Install-Package {0} -Version {1}", Model.Id, Model.Version) + }, - new ThirdPartyPackageManagerViewModel() - { - Id = "paket-cli", - Name = "Paket CLI", - CommandPrefix = "> ", - InstallPackageCommand = string.Format("paket add {0} --version {1}", Model.Id, Model.Version), - ContactUrl = "https://fsprojects.github.io/Paket/contact.html" - }, - }; + new PackageManagerViewModel() + { + Id = "dotnet-cli", + Name = ".NET CLI", + CommandPrefix = "> ", + InstallPackageCommand = string.Format("dotnet add package {0} --version {1}", Model.Id, Model.Version) + }, + + new ThirdPartyPackageManagerViewModel("https://fsprojects.github.io/Paket/contact.html") + { + Id = "paket-cli", + Name = "Paket CLI", + CommandPrefix = "> ", + InstallPackageCommand = string.Format("paket add {0} --version {1}", Model.Id, Model.Version), + } + }; + } } + @section SocialMeta { @if (!String.IsNullOrWhiteSpace(ViewBag.FacebookAppID)) { @@ -96,13 +116,23 @@ - @if (thirdPartyPackageManager != null) + + @switch (packageManager.AlertLevel) { - @ViewHelpers.AlertWarning( - @ - The NuGet Team does not provide support for this client. - Please contact its maintainers for support. - ) + case AlertLevel.Info: + @ViewHelpers.AlertInfo( + @ + @Html.Raw(packageManager.AlertMessage) + ); + break; + case AlertLevel.Warning: + @ViewHelpers.AlertWarning( + @ + @Html.Raw(packageManager.AlertMessage) + ); + break; + default: + break; } } From e2ddf4f20a5d7b371f00d5c0da8da8be86a76232 Mon Sep 17 00:00:00 2001 From: cmanu Date: Wed, 11 Apr 2018 18:08:15 -0700 Subject: [PATCH 03/32] GA - obfuscate ip and user names (#5791) Obfuscate the GA data and ip. --- src/NuGetGallery/App_Code/ViewHelpers.cshtml | 5 ++ src/NuGetGallery/Helpers/ObfuscationHelper.cs | 23 ++++++ src/NuGetGallery/NuGetGallery.csproj | 1 + .../Helpers/ObfuscationHelperFacts.cs | 72 +++++++++++++++++++ .../NuGetGallery.Facts.csproj | 1 + 5 files changed, 102 insertions(+) create mode 100644 src/NuGetGallery/Helpers/ObfuscationHelper.cs create mode 100644 tests/NuGetGallery.Facts/Helpers/ObfuscationHelperFacts.cs diff --git a/src/NuGetGallery/App_Code/ViewHelpers.cshtml b/src/NuGetGallery/App_Code/ViewHelpers.cshtml index 08a3606a07..a2e99219ea 100644 --- a/src/NuGetGallery/App_Code/ViewHelpers.cshtml +++ b/src/NuGetGallery/App_Code/ViewHelpers.cshtml @@ -1,4 +1,5 @@ @using System.Web.Mvc +@using System.Web.Routing @using System.Web.Mvc.Html @using Microsoft.Web.Helpers @using NuGetGallery @@ -353,6 +354,7 @@ var cookieService = DependencyResolver.Current.GetService(); if (cookieService.CanWriteNonEssentialCookies(Request)) { + var obfuscatedRequest = ObfuscationHelper.ObfuscateRequestUrl(new HttpContextWrapper(HttpContext.Current), RouteTable.Routes); } diff --git a/src/NuGetGallery/Helpers/ObfuscationHelper.cs b/src/NuGetGallery/Helpers/ObfuscationHelper.cs new file mode 100644 index 0000000000..c3dad1bce6 --- /dev/null +++ b/src/NuGetGallery/Helpers/ObfuscationHelper.cs @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Web; +using System.Web.Routing; + +namespace NuGetGallery.Helpers +{ + public class ObfuscationHelper + { + public static string ObfuscateRequestUrl(HttpContextBase httpContext, RouteCollection routes) + { + if (httpContext?.Request?.Url == null || routes == null) + { + return string.Empty; + } + + var route = routes.GetRouteData(httpContext)?.Route as Route; + return route == null ? string.Empty : route.ObfuscateUrlPath(httpContext.Request.Url.AbsolutePath.TrimStart('/')); + } + } +} \ No newline at end of file diff --git a/src/NuGetGallery/NuGetGallery.csproj b/src/NuGetGallery/NuGetGallery.csproj index 27a4d40e8c..7793b3b762 100644 --- a/src/NuGetGallery/NuGetGallery.csproj +++ b/src/NuGetGallery/NuGetGallery.csproj @@ -802,6 +802,7 @@ + diff --git a/tests/NuGetGallery.Facts/Helpers/ObfuscationHelperFacts.cs b/tests/NuGetGallery.Facts/Helpers/ObfuscationHelperFacts.cs new file mode 100644 index 0000000000..593f73371f --- /dev/null +++ b/tests/NuGetGallery.Facts/Helpers/ObfuscationHelperFacts.cs @@ -0,0 +1,72 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Web; +using System.Web.Routing; +using Moq; +using Xunit; + +namespace NuGetGallery.Helpers +{ + public class ObfuscationHelperFacts + { + public class TheObfuscateCurrentRequestUrlFacts + { + private const string _relativeTestPath = "profiles/user1"; + private RouteCollection _currentRoutes; + + public TheObfuscateCurrentRequestUrlFacts() + { + if (_currentRoutes == null) + { + _currentRoutes = new RouteCollection(); + Routes.RegisterApiV2Routes(_currentRoutes); + Routes.RegisterUIRoutes(_currentRoutes); + } + } + + [Fact] + public void WithNullContextReturnsEmptyString() + { + // Assert + Assert + var result = ObfuscationHelper.ObfuscateRequestUrl(null, _currentRoutes); + Assert.Equal("", result); + } + + [Fact] + public void WithNullRoutesReturnsEmptyString() + { + //Arrange + var context = GetMockedHttpContext(); + + // Assert + Assert + var result = ObfuscationHelper.ObfuscateRequestUrl(context, null); + Assert.Equal("", result); + } + + [Fact] + public void ValidData() + { + //Arrange + var context = GetMockedHttpContext(); + + // Assert + Assert + var result = ObfuscationHelper.ObfuscateRequestUrl(context, _currentRoutes); + Assert.Equal("profiles/ObfuscatedUserName", result); + } + + private HttpContextBase GetMockedHttpContext() + { + var context = new Mock(); + var request = new Mock(); + context.Setup(ctx => ctx.Request).Returns(request.Object); + + request.Setup(req => req.Url).Returns(new Uri($"https://localhost/{_relativeTestPath}")); + request.Setup(req => req.AppRelativeCurrentExecutionFilePath).Returns($"~/{_relativeTestPath}"); + return context.Object; + } + } + } +} + diff --git a/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj b/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj index 654fce1407..a7c6583d2e 100644 --- a/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj +++ b/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj @@ -413,6 +413,7 @@ + From 2447f3ab3d2cd58f3c028e4456d907de44c28b74 Mon Sep 17 00:00:00 2001 From: Scott Bommarito Date: Fri, 13 Apr 2018 14:19:24 -0700 Subject: [PATCH 04/32] Fix accessibility issues identified by tool (#5795) --- src/Bootstrap/dist/css/bootstrap-theme.css | 12 ------ src/Bootstrap/less/theme/base.less | 17 --------- src/NuGetGallery/App_Code/ViewHelpers.cshtml | 4 +- src/NuGetGallery/Scripts/gallery/common.js | 38 +++++++++++++++++++ .../Views/Authentication/SignIn.cshtml | 12 +++--- .../Authentication/_SigninAssistance.cshtml | 4 +- .../Organizations/ManageOrganization.cshtml | 4 +- src/NuGetGallery/Views/Packages/Delete.cshtml | 2 +- .../Views/Packages/DisplayPackage.cshtml | 4 +- src/NuGetGallery/Views/Packages/Edit.cshtml | 6 +-- .../Views/Packages/ManagePackageOwners.cshtml | 2 +- .../Views/Packages/UploadPackage.cshtml | 6 +-- .../Views/Packages/_EditForm.cshtml | 2 +- .../Views/Packages/_VerifyForm.cshtml | 2 +- .../Views/Packages/_VerifyMetadata.cshtml | 2 +- .../Views/Shared/Gallery/Header.cshtml | 18 ++++----- .../Shared/_AccountChangeNotifications.cshtml | 4 +- .../Views/Statistics/Index.cshtml | 2 +- .../Views/Statistics/PackageVersions.cshtml | 2 +- .../Views/Statistics/Packages.cshtml | 2 +- .../Views/Statistics/_PivotTable.cshtml | 2 +- src/NuGetGallery/Views/Users/ApiKeys.cshtml | 4 +- src/NuGetGallery/Views/Users/Profiles.cshtml | 6 +-- ...UserAccountChangeExternalCredential.cshtml | 2 +- .../Users/_UserAccountChangePassword.cshtml | 2 +- .../_UserPackagesListForDeletedAccount.cshtml | 2 +- 26 files changed, 85 insertions(+), 78 deletions(-) diff --git a/src/Bootstrap/dist/css/bootstrap-theme.css b/src/Bootstrap/dist/css/bootstrap-theme.css index ae926ac6d0..3a02595ed9 100644 --- a/src/Bootstrap/dist/css/bootstrap-theme.css +++ b/src/Bootstrap/dist/css/bootstrap-theme.css @@ -87,21 +87,9 @@ body h3 { height: auto; padding-bottom: 75px; } -.main-container .page-subheading { - color: #777; -} .navbar-logo { margin: 8px 20px 0 0; } -.navbar-seperated .seperator { - display: block; - padding: 11px 0; - font-weight: 100; - color: #fff; -} -.navbar-seperated .seperator > span::after { - content: " | "; -} .dropdown-menu { padding-top: 20px; padding-bottom: 20px; diff --git a/src/Bootstrap/less/theme/base.less b/src/Bootstrap/less/theme/base.less index 934c451634..304e495fda 100644 --- a/src/Bootstrap/less/theme/base.less +++ b/src/Bootstrap/less/theme/base.less @@ -103,29 +103,12 @@ body { .main-container { padding-bottom: 75px; height: auto; - - .page-subheading { - color: #777; - } } .navbar-logo { margin: 8px 20px 0 0; } -.navbar-seperated { - .seperator { - padding: 11px 0; - color: #fff; - display: block; - font-weight: 100; - - > span::after { - content: " | "; - } - } -} - .dropdown-menu { padding-top: (@padding-large-vertical * 2); padding-bottom: (@padding-large-vertical * 2); diff --git a/src/NuGetGallery/App_Code/ViewHelpers.cshtml b/src/NuGetGallery/App_Code/ViewHelpers.cshtml index a2e99219ea..e9663a8ff7 100644 --- a/src/NuGetGallery/App_Code/ViewHelpers.cshtml +++ b/src/NuGetGallery/App_Code/ViewHelpers.cshtml @@ -455,7 +455,7 @@ var hlp = new AccordionHelper(name, formModelStatePrefix, expanded, page); @helper AjaxAntiForgeryToken(System.Web.Mvc.HtmlHelper html) { -
+ @html.AntiForgeryToken()
} @@ -537,7 +537,7 @@ var hlp = new AccordionHelper(name, formModelStatePrefix, expanded, page); @if (!disabled) { + aria-expanded="@(expanded ? "true" : "false")" aria-controls="@id-container" id="show-@id-container"> @title(MvcHtmlString.Empty) diff --git a/src/NuGetGallery/Scripts/gallery/common.js b/src/NuGetGallery/Scripts/gallery/common.js index e3e778eb05..5a34352c8a 100644 --- a/src/NuGetGallery/Scripts/gallery/common.js +++ b/src/NuGetGallery/Scripts/gallery/common.js @@ -50,10 +50,48 @@ } else { error.insertAfter(element); } + }, + showErrors: function (errorMap, errorList) { + this.defaultShowErrors(); + + // By default, showErrors adds an aria-describedby attribute to every field that it validates, even if it finds no issues. + // This is a problem, because the aria-describedby attribute will then link to an empty element. + // This code removes the aria-describedby if the describing element is missing or empty. + var i; + for (i = 0; this.errorList[i]; i++) { + removeInvalidAriaDescribedBy(this.errorList[i].element); + } + + for (i = 0; this.successList[i]; i++) { + removeInvalidAriaDescribedBy(this.successList[i]); + } } }); } + function removeInvalidAriaDescribedBy(element) { + var describedBy = element.getAttribute("aria-describedby"); + if (!describedBy) { + return; + } + + var ids = describedBy.split(" ") + .filter(function (describedById) { + if (!describedById) { + return false; + } + + var describedByElement = $("#" + describedById); + return describedByElement && describedByElement.text(); + }); + + if (ids.length) { + element.setAttribute("aria-describedby", ids.join(" ")); + } else { + element.removeAttribute("aria-describedby"); + } + } + nuget.parseNumber = function (unparsedValue) { unparsedValue = ('' + unparsedValue).replace(/,/g, ''); var parsedValue = parseInt(unparsedValue); diff --git a/src/NuGetGallery/Views/Authentication/SignIn.cshtml b/src/NuGetGallery/Views/Authentication/SignIn.cshtml index 2ad71927d4..1ec3d2d40b 100644 --- a/src/NuGetGallery/Views/Authentication/SignIn.cshtml +++ b/src/NuGetGallery/Views/Authentication/SignIn.cshtml @@ -43,13 +43,15 @@ - +
} + +