Статьи

Использование Google Analytics Embed API для создания панели инструментов

Около полутора лет назад я создал демо-версию ( Proof of Concept — Dashboard for Google Analytics ) панели инструментов Google Analytics. Это демо было полностью на стороне клиента и использовало библиотеки API, созданные Google. По большей части процесс был довольно прост. После того, как я выяснил, как аутентифицировать пользователя и запросить данные, я потратил больше времени на то, чтобы он выглядел красиво, чем на самом деле работал с API, что хорошо. Совсем недавно я обнаружил новый API Analytics Embed . Отличительной особенностью Embed API является то, что он значительно упрощает аспект аутентификации / авторизации при получении аналитических данных и даже предоставляет встроенные возможности построения диаграмм. Пример блога ( Быстрый пример API Google Analytics Embed)) и я подумал, что было бы забавно пересмотреть мою концепцию панели инструментов с помощью этого более простого API.

Прежде чем я покажу код этой демонстрации, позвольте мне поделиться снимком экрана, чтобы вы могли увидеть его в действии. Я переключился на новую библиотеку графиков для этой демонстрации ( ChartJS ) главным образом потому, что Google использовал ее в одной из своих демонстраций. Я не тратил много времени на то, чтобы сделать это красиво — я собирался получить «быстрое воздействие» с точки зрения визуальных эффектов Очевидно, что могло бы быть и лучше.

Shot1

Каждый график представляет одно из моих свойств. Каждая строка представляет количество просмотров страниц в сутки, причем темно-синий — самая последняя страница за семь дней, а светло-серый — за семь дней до этого.

Код состоит из нескольких частей. Аутентификация, как я уже сказал, почти полностью обрабатывается API встраивания.

gapi.analytics.ready(function() {

  var CLIENT_ID = '818125206534-g1r0datdtu9serq2pf9cp5vkuih3h8pv.apps.googleusercontent.com';

  gapi.analytics.auth.authorize({
    container: 'auth-button',
    clientid: CLIENT_ID,
    userInfoLabel:""
  });

  gapi.analytics.auth.on('success', function(response) {
    //hide the auth-button
    document.querySelector("#auth-button").style.display='none';
    console.log("get profiles");
    getProfiles(function(profs) {
      window.profiles = profs;
      processProfiles();      
    });

  });

  Chart.defaults.global.animationSteps = 60;
  Chart.defaults.global.animationEasing = 'easeInOutQuart';
  Chart.defaults.global.responsive = true;
  Chart.defaults.global.maintainAspectRatio = false;

});

Чтобы получить профили для моей учетной записи, я использую API управления. getProfilesобрабатывает выборку и кеширование, этот результат. (Я использую кеширование, как и планировал, все еще планирую, добавив несколько дополнительных параметров фильтрации в отчет.)

function getProfiles(cb) {
  //do we have a cached version?
  if(sessionStorage["gaProfiles"]) {
    console.log("profiles fetched from cache");
    cb(JSON.parse(sessionStorage["gaProfiles"]));
    return;
  }

  gapi.client.analytics.management.accounts.list().then(function(res) { 
    var accountId = res.result.items[0].id;
    var profiles = [];
    gapi.client.analytics.management.webproperties.list({'accountId': accountId}).then(function(res) {

    res.result.items.forEach(function(item) {
    if(item.defaultProfileId) profiles.push({id:"ga:"+item.defaultProfileId,name:item.name});
    });
      sessionStorage["gaProfiles"] = JSON.stringify(profiles);    
      cb(profiles);      
    });
  });
}

Обратите внимание, что я не использую обещания в этом блоке, и это ошибка. Я использую его чуть позже в другой функции, поэтому мне нужно (ну, я хочу) быть последовательным. Теперь самое интересное. Для всех моих свойств мне нужно получить данные для каждого сайта. Мне удалось скопировать код из одной из демонстраций Google, но я быстро столкнулся с проблемами ограничения скорости. Чтобы обойти это, я однопотоковую звонки и добавить небольшую задержку.

//Credit: https://ga-dev-tools.appspot.com/embed-api/third-party-visualizations/
function query(params) {
  return new Promise(function(resolve, reject) {
    var data = new gapi.analytics.report.Data({query: params});
    data.once('success', function(response) { resolve(response); })
        .once('error', function(response) { reject(response); })
        .execute();
  });
}

function makeCanvas(id) {
  var container = document.getElementById(id);
  var canvas = document.createElement('canvas');
  var ctx = canvas.getContext('2d');

  container.innerHTML = '';
  canvas.width = container.offsetWidth;
  canvas.height = container.offsetHeight;
  container.appendChild(canvas);

  return ctx;
}

function processProfiles() {
  console.log("working on profile "+profiles[curProfile].name);

  var now = moment();
  var id = profiles[curProfile].id;

  var thisWeek = query({
    'ids': id,
    'dimensions': 'ga:date,ga:nthDay',
    'metrics': 'ga:pageviews',
    'start-date': moment(now).subtract(8, 'day').format('YYYY-MM-DD'),
    'end-date': moment(now).subtract(1,'day').format('YYYY-MM-DD')
  });

  var lastWeek = query({
    'ids': id,
    'dimensions': 'ga:date,ga:nthDay',
    'metrics': 'ga:pageviews',
    'start-date': moment(now).subtract(15, 'day').subtract(1, 'week')
    .format('YYYY-MM-DD'),
    'end-date': moment(now).subtract(8, 'day').subtract(1, 'week')
    .format('YYYY-MM-DD')
  });


  Promise.all([thisWeek, lastWeek]).then(function(results) {
    console.log("Promise.all");console.dir(results);

    var data1 = results[0].rows.map(function(row) { return +row[2]; });
    var data2 = results[1].rows.map(function(row) { return +row[2]; });
    var labels = results[1].rows.map(function(row) { return +row[0]; });

    var totalThisWeek = results[0].totalsForAllResults["ga:pageviews"];
    var totalLastWeek = results[1].totalsForAllResults["ga:pageviews"];
    var percChange = (((totalThisWeek - totalLastWeek) / totalLastWeek) * 100).toFixed(2);
    console.log(totalLastWeek, totalThisWeek, percChange);

    labels = labels.map(function(label) {
      return moment(label, 'YYYYMMDD').format('ddd');
    });

    var data = {
      labels : labels,
      datasets : [
        {
          label: 'Last Week',
          fillColor : 'rgba(220,220,220,0.5)',
          strokeColor : 'rgba(220,220,220,1)',
          pointColor : 'rgba(220,220,220,1)',
          pointStrokeColor : '#fff',
          data : data2
        },
        {
          label: 'This Week',
          fillColor : 'rgba(151,187,205,0.5)',
          strokeColor : 'rgba(151,187,205,1)',
          pointColor : 'rgba(151,187,205,1)',
          pointStrokeColor : '#fff',
          data : data1
        }
      ]
    };

    var titleStr = profiles[curProfile].name + " ";
    if(totalLastWeek > 0 && totalThisWeek > 0) {
      if(percChange < 0) {
        titleStr += "<span class='down'>(Down "+Math.abs(percChange) + "%)</span>";
      } else {
        titleStr += "<span class='up'>(Up "+percChange + "%)</span>";      
      }
    }

  $("body").append("<div class='reportContainer'><div class='chartTitleContainer'>"+titleStr+"</div><div class='chartContainer' id='chart-"+curProfile+"-container'></div></div>");

    new Chart(makeCanvas('chart-'+curProfile+'-container')).Line(data);

    if(curProfile+1 < profiles.length) {
      curProfile++;
      //settimeout to try to avoid GA rate limits
      setTimeout(processProfiles,200);
    }
  });
}

И это в основном все. Как я уже сказал, я хотел бы добавить несколько вариантов к этому. В частности, возможность сравнить текущие и прошедшие 30 дней, а также этот год с прошлым. Я также хотел бы иметь возможность отклонять / скрывать некоторые сайты, которые меня не интересуют. Я мог бы использовать LocalStorage, чтобы запомнить эти свойства и скрыть их автоматически.

Хотите увидеть полный код и проверить его самостоятельно? Проверьте онлайн демо здесь: http://static.raymondcamden.com/ga_embed/test10.html