diff --git a/app/assets/javascripts/monitoring/components/charts/area.vue b/app/assets/javascripts/monitoring/components/charts/area.vue
index 90c764587a3c044f10f3bb61fb936e02677167ea..78d97a3c122fb4d02d0390372aec577aa0d9647e 100644
--- a/app/assets/javascripts/monitoring/components/charts/area.vue
+++ b/app/assets/javascripts/monitoring/components/charts/area.vue
@@ -12,6 +12,9 @@ import { graphDataValidatorForValues } from '../../utils';
 
 let debouncedResize;
 
+// TODO: Remove this component in favor of the more general time_series.vue
+// Please port all changes here to time_series.vue as well.
+
 export default {
   components: {
     GlAreaChart,
diff --git a/app/assets/javascripts/monitoring/components/charts/time_series.vue b/app/assets/javascripts/monitoring/components/charts/time_series.vue
new file mode 100644
index 0000000000000000000000000000000000000000..2fdc75f63cad01f228faec607d26831277d7bf68
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/charts/time_series.vue
@@ -0,0 +1,334 @@
+<script>
+import { __ } from '~/locale';
+import { mapState } from 'vuex';
+import { GlLink, GlButton } from '@gitlab/ui';
+import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
+import dateFormat from 'dateformat';
+import { debounceByAnimationFrame, roundOffFloat } from '~/lib/utils/common_utils';
+import { getSvgIconPathContent } from '~/lib/utils/icon_utils';
+import Icon from '~/vue_shared/components/icon.vue';
+import { chartHeight, graphTypes, lineTypes, symbolSizes, dateFormats } from '../../constants';
+import { makeDataSeries } from '~/helpers/monitor_helper';
+import { graphDataValidatorForValues } from '../../utils';
+
+let debouncedResize;
+
+export default {
+  components: {
+    GlAreaChart,
+    GlLineChart,
+    GlButton,
+    GlChartSeriesLabel,
+    GlLink,
+    Icon,
+  },
+  inheritAttrs: false,
+  props: {
+    graphData: {
+      type: Object,
+      required: true,
+      validator: graphDataValidatorForValues.bind(null, false),
+    },
+    containerWidth: {
+      type: Number,
+      required: true,
+    },
+    deploymentData: {
+      type: Array,
+      required: false,
+      default: () => [],
+    },
+    projectPath: {
+      type: String,
+      required: false,
+      default: '',
+    },
+    showBorder: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+    thresholds: {
+      type: Array,
+      required: false,
+      default: () => [],
+    },
+  },
+  data() {
+    return {
+      tooltip: {
+        title: '',
+        content: [],
+        commitUrl: '',
+        isDeployment: false,
+        sha: '',
+      },
+      width: 0,
+      height: chartHeight,
+      svgs: {},
+      primaryColor: null,
+    };
+  },
+  computed: {
+    ...mapState('monitoringDashboard', ['exportMetricsToCsvEnabled']),
+    chartData() {
+      // Transforms & supplements query data to render appropriate labels & styles
+      // Input: [{ queryAttributes1 }, { queryAttributes2 }]
+      // Output: [{ seriesAttributes1 }, { seriesAttributes2 }]
+      return this.graphData.queries.reduce((acc, query) => {
+        const { appearance } = query;
+        const lineType =
+          appearance && appearance.line && appearance.line.type
+            ? appearance.line.type
+            : lineTypes.default;
+        const lineWidth =
+          appearance && appearance.line && appearance.line.width
+            ? appearance.line.width
+            : undefined;
+        const areaStyle = {
+          opacity:
+            appearance && appearance.area && typeof appearance.area.opacity === 'number'
+              ? appearance.area.opacity
+              : undefined,
+        };
+
+        const series = makeDataSeries(query.result, {
+          name: this.formatLegendLabel(query),
+          lineStyle: {
+            type: lineType,
+            width: lineWidth,
+          },
+          showSymbol: false,
+          areaStyle: this.graphData.type === 'area-chart' ? areaStyle : undefined,
+        });
+
+        return acc.concat(series);
+      }, []);
+    },
+    chartOptions() {
+      return {
+        xAxis: {
+          name: __('Time'),
+          type: 'time',
+          axisLabel: {
+            formatter: date => dateFormat(date, dateFormats.timeOfDay),
+          },
+          axisPointer: {
+            snap: true,
+          },
+        },
+        yAxis: {
+          name: this.yAxisLabel,
+          axisLabel: {
+            formatter: num => roundOffFloat(num, 3).toString(),
+          },
+        },
+        series: this.scatterSeries,
+        dataZoom: this.dataZoomConfig,
+      };
+    },
+    dataZoomConfig() {
+      const handleIcon = this.svgs['scroll-handle'];
+
+      return handleIcon ? { handleIcon } : {};
+    },
+    earliestDatapoint() {
+      return this.chartData.reduce((acc, series) => {
+        const { data } = series;
+        const { length } = data;
+        if (!length) {
+          return acc;
+        }
+
+        const [first] = data[0];
+        const [last] = data[length - 1];
+        const seriesEarliest = first < last ? first : last;
+
+        return seriesEarliest < acc || acc === null ? seriesEarliest : acc;
+      }, null);
+    },
+    glChartComponent() {
+      const chartTypes = {
+        'area-chart': GlAreaChart,
+        'line-chart': GlLineChart,
+      };
+      return chartTypes[this.graphData.type] || GlAreaChart;
+    },
+    isMultiSeries() {
+      return this.tooltip.content.length > 1;
+    },
+    recentDeployments() {
+      return this.deploymentData.reduce((acc, deployment) => {
+        if (deployment.created_at >= this.earliestDatapoint) {
+          const { id, created_at, sha, ref, tag } = deployment;
+          acc.push({
+            id,
+            createdAt: created_at,
+            sha,
+            commitUrl: `${this.projectPath}/commit/${sha}`,
+            tag,
+            tagUrl: tag ? `${this.tagsPath}/${ref.name}` : null,
+            ref: ref.name,
+            showDeploymentFlag: false,
+          });
+        }
+
+        return acc;
+      }, []);
+    },
+    scatterSeries() {
+      return {
+        type: graphTypes.deploymentData,
+        data: this.recentDeployments.map(deployment => [deployment.createdAt, 0]),
+        symbol: this.svgs.rocket,
+        symbolSize: symbolSizes.default,
+        itemStyle: {
+          color: this.primaryColor,
+        },
+      };
+    },
+    yAxisLabel() {
+      return `${this.graphData.y_label}`;
+    },
+    csvText() {
+      const chartData = this.chartData[0].data;
+      const header = `timestamp,${this.graphData.y_label}\r\n`; // eslint-disable-line @gitlab/i18n/no-non-i18n-strings
+      return chartData.reduce((csv, data) => {
+        const row = data.join(',');
+        return `${csv}${row}\r\n`;
+      }, header);
+    },
+    downloadLink() {
+      const data = new Blob([this.csvText], { type: 'text/plain' });
+      return window.URL.createObjectURL(data);
+    },
+  },
+  watch: {
+    containerWidth: 'onResize',
+  },
+  beforeDestroy() {
+    window.removeEventListener('resize', debouncedResize);
+  },
+  created() {
+    debouncedResize = debounceByAnimationFrame(this.onResize);
+    window.addEventListener('resize', debouncedResize);
+    this.setSvg('rocket');
+    this.setSvg('scroll-handle');
+  },
+  methods: {
+    formatLegendLabel(query) {
+      return `${query.label}`;
+    },
+    formatTooltipText(params) {
+      this.tooltip.title = dateFormat(params.value, dateFormats.default);
+      this.tooltip.content = [];
+      params.seriesData.forEach(dataPoint => {
+        const [xVal, yVal] = dataPoint.value;
+        this.tooltip.isDeployment = dataPoint.componentSubType === graphTypes.deploymentData;
+        if (this.tooltip.isDeployment) {
+          const [deploy] = this.recentDeployments.filter(
+            deployment => deployment.createdAt === xVal,
+          );
+          this.tooltip.sha = deploy.sha.substring(0, 8);
+          this.tooltip.commitUrl = deploy.commitUrl;
+        } else {
+          const { seriesName, color } = dataPoint;
+          const value = yVal.toFixed(3);
+          this.tooltip.content.push({
+            name: seriesName,
+            value,
+            color,
+          });
+        }
+      });
+    },
+    setSvg(name) {
+      getSvgIconPathContent(name)
+        .then(path => {
+          if (path) {
+            this.$set(this.svgs, name, `path://${path}`);
+          }
+        })
+        .catch(e => {
+          // eslint-disable-next-line no-console, @gitlab/i18n/no-non-i18n-strings
+          console.error('SVG could not be rendered correctly: ', e);
+        });
+    },
+    onChartUpdated(chart) {
+      [this.primaryColor] = chart.getOption().color;
+    },
+    onResize() {
+      if (!this.$refs.chart) return;
+      const { width } = this.$refs.chart.$el.getBoundingClientRect();
+      this.width = width;
+    },
+  },
+};
+</script>
+
+<template>
+  <div class="prometheus-graph col-12 col-lg-6" :class="[showBorder ? 'p-2' : 'p-0']">
+    <div :class="{ 'prometheus-graph-embed w-100 p-3': showBorder }">
+      <div class="prometheus-graph-header">
+        <h5 class="prometheus-graph-title js-graph-title">{{ graphData.title }}</h5>
+        <gl-button
+          v-if="exportMetricsToCsvEnabled"
+          :href="downloadLink"
+          :title="__('Download CSV')"
+          :aria-label="__('Download CSV')"
+          style="margin-left: 200px;"
+          download="chart_metrics.csv"
+        >
+          {{ __('Download CSV') }}
+        </gl-button>
+        <div class="prometheus-graph-widgets js-graph-widgets">
+          <slot></slot>
+        </div>
+      </div>
+
+      <component
+        :is="glChartComponent"
+        ref="chart"
+        v-bind="$attrs"
+        :data="chartData"
+        :option="chartOptions"
+        :format-tooltip-text="formatTooltipText"
+        :thresholds="thresholds"
+        :width="width"
+        :height="height"
+        @updated="onChartUpdated"
+      >
+        <template v-if="tooltip.isDeployment">
+          <template slot="tooltipTitle">
+            {{ __('Deployed') }}
+          </template>
+          <div slot="tooltipContent" class="d-flex align-items-center">
+            <icon name="commit" class="mr-2" />
+            <gl-link :href="tooltip.commitUrl">{{ tooltip.sha }}</gl-link>
+          </div>
+        </template>
+        <template v-else>
+          <template slot="tooltipTitle">
+            <div class="text-nowrap">
+              {{ tooltip.title }}
+            </div>
+          </template>
+          <template slot="tooltipContent">
+            <div
+              v-for="(content, key) in tooltip.content"
+              :key="key"
+              class="d-flex justify-content-between"
+            >
+              <gl-chart-series-label :color="isMultiSeries ? content.color : ''">
+                {{ content.name }}
+              </gl-chart-series-label>
+              <div class="prepend-left-32">
+                {{ content.value }}
+              </div>
+            </div>
+          </template>
+        </template>
+      </component>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index ebd610af7b6b37659376223553b6c9b8e358dd56..d330ceb836c1f097f5ddeb52eadb973ae394ee57 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -15,7 +15,7 @@ import Icon from '~/vue_shared/components/icon.vue';
 import { getParameterValues, mergeUrlParams } from '~/lib/utils/url_utility';
 import invalidUrl from '~/lib/utils/invalid_url';
 import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue';
-import MonitorAreaChart from './charts/area.vue';
+import MonitorTimeSeriesChart from './charts/time_series.vue';
 import MonitorSingleStatChart from './charts/single_stat.vue';
 import GraphGroup from './graph_group.vue';
 import EmptyState from './empty_state.vue';
@@ -26,7 +26,7 @@ let sidebarMutationObserver;
 
 export default {
   components: {
-    MonitorAreaChart,
+    MonitorTimeSeriesChart,
     MonitorSingleStatChart,
     PanelType,
     GraphGroup,
@@ -465,7 +465,7 @@ export default {
           />
         </template>
         <template v-else>
-          <monitor-area-chart
+          <monitor-time-series-chart
             v-for="(graphData, graphIndex) in chartsWithData(groupData.metrics)"
             :key="graphIndex"
             :graph-data="graphData"
@@ -473,7 +473,7 @@ export default {
             :thresholds="getGraphAlertValues(graphData.queries)"
             :container-width="elWidth"
             :project-path="projectPath"
-            group-id="monitor-area-chart"
+            group-id="monitor-time-series-chart"
           >
             <div class="d-flex align-items-center">
               <alert-widget
@@ -515,7 +515,7 @@ export default {
                 </gl-dropdown-item>
               </gl-dropdown>
             </div>
-          </monitor-area-chart>
+          </monitor-time-series-chart>
         </template>
       </graph-group>
     </div>
diff --git a/app/assets/javascripts/monitoring/constants.js b/app/assets/javascripts/monitoring/constants.js
index d7d895227325e3a9bab203f0ef9c94733363cedf..13aba3d9f4472b2e44c14d7b15ed86501da55476 100644
--- a/app/assets/javascripts/monitoring/constants.js
+++ b/app/assets/javascripts/monitoring/constants.js
@@ -8,6 +8,10 @@ export const graphTypes = {
   deploymentData: 'scatter',
 };
 
+export const symbolSizes = {
+  default: 14,
+};
+
 export const lineTypes = {
   default: 'solid',
 };
@@ -21,6 +25,11 @@ export const timeWindows = {
   oneWeek: __('1 week'),
 };
 
+export const dateFormats = {
+  timeOfDay: 'h:MM TT',
+  default: 'dd mmm yyyy, h:MMTT',
+};
+
 export const secondsIn = {
   thirtyMinutes: 60 * 30,
   threeHours: 60 * 60 * 3,
diff --git a/changelogs/unreleased/65412-add-support-for-line-charts.yml b/changelogs/unreleased/65412-add-support-for-line-charts.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cb9043596b74d9bd2a204cd76afe57783859cb55
--- /dev/null
+++ b/changelogs/unreleased/65412-add-support-for-line-charts.yml
@@ -0,0 +1,5 @@
+---
+title: Create component to display area and line charts in monitor dashboards
+merge_request: 31639
+author:
+type: added
diff --git a/config/prometheus/common_metrics.yml b/config/prometheus/common_metrics.yml
index 32475ef83808cf76d38923609a8bb62eaf0b8ffb..08504d6f7d59c1614ae71978e20e377e261668f3 100644
--- a/config/prometheus/common_metrics.yml
+++ b/config/prometheus/common_metrics.yml
@@ -166,7 +166,7 @@ panel_groups:
       label: Total (cores)
       unit: "cores"
   - title: "Memory Usage (Pod average)"
-    type: "area-chart"
+    type: "line-chart"
     y_label: "Memory Used per Pod (MB)"
     weight: 2
     metrics:
@@ -175,7 +175,7 @@ panel_groups:
       label: Pod average (MB)
       unit: MB
   - title: "Canary: Memory Usage (Pod Average)"
-    type: "area-chart"
+    type: "line-chart"
     y_label: "Memory Used per Pod (MB)"
     weight: 2
     metrics:
@@ -185,7 +185,7 @@ panel_groups:
       unit: MB
       track: canary
   - title: "Core Usage (Pod Average)"
-    type: "area-chart"
+    type: "line-chart"
     y_label: "Cores per Pod"
     weight: 1
     metrics:
@@ -194,7 +194,7 @@ panel_groups:
       label: Pod average (cores)
       unit: "cores"
   - title: "Canary: Core Usage (Pod Average)"
-    type: "area-chart"
+    type: "line-chart"
     y_label: "Cores per Pod"
     weight: 1
     metrics:
diff --git a/db/migrate/20190814205640_import_common_metrics_line_charts.rb b/db/migrate/20190814205640_import_common_metrics_line_charts.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1c28d686a428fb358ea7856f1dc3d680bdb188b5
--- /dev/null
+++ b/db/migrate/20190814205640_import_common_metrics_line_charts.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class ImportCommonMetricsLineCharts < ActiveRecord::Migration[5.2]
+  DOWNTIME = false
+
+  def up
+    ::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
+  end
+
+  def down
+    # no-op
+  end
+end
diff --git a/spec/javascripts/monitoring/charts/time_series_spec.js b/spec/javascripts/monitoring/charts/time_series_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..d145a64e8d044e70b36f2bd917af54909107ed59
--- /dev/null
+++ b/spec/javascripts/monitoring/charts/time_series_spec.js
@@ -0,0 +1,335 @@
+import { shallowMount } from '@vue/test-utils';
+import { createStore } from '~/monitoring/stores';
+import { GlLink } from '@gitlab/ui';
+import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
+import { shallowWrapperContainsSlotText } from 'spec/helpers/vue_test_utils_helper';
+import TimeSeries from '~/monitoring/components/charts/time_series.vue';
+import * as types from '~/monitoring/stores/mutation_types';
+import { TEST_HOST } from 'spec/test_constants';
+import MonitoringMock, { deploymentData, mockProjectPath } from '../mock_data';
+
+describe('Time series component', () => {
+  const mockSha = 'mockSha';
+  const mockWidgets = 'mockWidgets';
+  const mockSvgPathContent = 'mockSvgPathContent';
+  const projectPath = `${TEST_HOST}${mockProjectPath}`;
+  const commitUrl = `${projectPath}/commit/${mockSha}`;
+  let mockGraphData;
+  let makeTimeSeriesChart;
+  let spriteSpy;
+  let store;
+
+  beforeEach(() => {
+    store = createStore();
+    store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, MonitoringMock.data);
+    store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData);
+    store.dispatch('monitoringDashboard/setFeatureFlags', { exportMetricsToCsvEnabled: true });
+    [mockGraphData] = store.state.monitoringDashboard.groups[0].metrics;
+
+    makeTimeSeriesChart = (graphData, type) =>
+      shallowMount(TimeSeries, {
+        propsData: {
+          graphData: { ...graphData, type },
+          containerWidth: 0,
+          deploymentData: store.state.monitoringDashboard.deploymentData,
+          projectPath,
+        },
+        slots: {
+          default: mockWidgets,
+        },
+        sync: false,
+        store,
+      });
+
+    spriteSpy = spyOnDependency(TimeSeries, 'getSvgIconPathContent').and.callFake(
+      () => new Promise(resolve => resolve(mockSvgPathContent)),
+    );
+  });
+
+  describe('general functions', () => {
+    let timeSeriesChart;
+
+    beforeEach(() => {
+      timeSeriesChart = makeTimeSeriesChart(mockGraphData, 'area-chart');
+    });
+
+    it('renders chart title', () => {
+      expect(timeSeriesChart.find('.js-graph-title').text()).toBe(mockGraphData.title);
+    });
+
+    it('contains graph widgets from slot', () => {
+      expect(timeSeriesChart.find('.js-graph-widgets').text()).toBe(mockWidgets);
+    });
+
+    describe('when exportMetricsToCsvEnabled is disabled', () => {
+      beforeEach(() => {
+        store.dispatch('monitoringDashboard/setFeatureFlags', { exportMetricsToCsvEnabled: false });
+      });
+
+      it('does not render the Download CSV button', done => {
+        timeSeriesChart.vm.$nextTick(() => {
+          expect(timeSeriesChart.contains('glbutton-stub')).toBe(false);
+          done();
+        });
+      });
+    });
+
+    describe('methods', () => {
+      describe('formatTooltipText', () => {
+        const mockDate = deploymentData[0].created_at;
+        const mockCommitUrl = deploymentData[0].commitUrl;
+        const generateSeriesData = type => ({
+          seriesData: [
+            {
+              seriesName: timeSeriesChart.vm.chartData[0].name,
+              componentSubType: type,
+              value: [mockDate, 5.55555],
+              seriesIndex: 0,
+            },
+          ],
+          value: mockDate,
+        });
+
+        describe('when series is of line type', () => {
+          beforeEach(done => {
+            timeSeriesChart.vm.formatTooltipText(generateSeriesData('line'));
+            timeSeriesChart.vm.$nextTick(done);
+          });
+
+          it('formats tooltip title', () => {
+            expect(timeSeriesChart.vm.tooltip.title).toBe('31 May 2017, 9:23PM');
+          });
+
+          it('formats tooltip content', () => {
+            const name = 'Core Usage';
+            const value = '5.556';
+            const seriesLabel = timeSeriesChart.find(GlChartSeriesLabel);
+
+            expect(seriesLabel.vm.color).toBe('');
+            expect(shallowWrapperContainsSlotText(seriesLabel, 'default', name)).toBe(true);
+            expect(timeSeriesChart.vm.tooltip.content).toEqual([{ name, value, color: undefined }]);
+            expect(
+              shallowWrapperContainsSlotText(
+                timeSeriesChart.find(GlAreaChart),
+                'tooltipContent',
+                value,
+              ),
+            ).toBe(true);
+          });
+        });
+
+        describe('when series is of scatter type', () => {
+          beforeEach(() => {
+            timeSeriesChart.vm.formatTooltipText(generateSeriesData('scatter'));
+          });
+
+          it('formats tooltip title', () => {
+            expect(timeSeriesChart.vm.tooltip.title).toBe('31 May 2017, 9:23PM');
+          });
+
+          it('formats tooltip sha', () => {
+            expect(timeSeriesChart.vm.tooltip.sha).toBe('f5bcd1d9');
+          });
+
+          it('formats tooltip commit url', () => {
+            expect(timeSeriesChart.vm.tooltip.commitUrl).toBe(mockCommitUrl);
+          });
+        });
+      });
+
+      describe('setSvg', () => {
+        const mockSvgName = 'mockSvgName';
+
+        beforeEach(done => {
+          timeSeriesChart.vm.setSvg(mockSvgName);
+          timeSeriesChart.vm.$nextTick(done);
+        });
+
+        it('gets svg path content', () => {
+          expect(spriteSpy).toHaveBeenCalledWith(mockSvgName);
+        });
+
+        it('sets svg path content', () => {
+          timeSeriesChart.vm.$nextTick(() => {
+            expect(timeSeriesChart.vm.svgs[mockSvgName]).toBe(`path://${mockSvgPathContent}`);
+          });
+        });
+      });
+
+      describe('onResize', () => {
+        const mockWidth = 233;
+
+        beforeEach(() => {
+          spyOn(Element.prototype, 'getBoundingClientRect').and.callFake(() => ({
+            width: mockWidth,
+          }));
+          timeSeriesChart.vm.onResize();
+        });
+
+        it('sets area chart width', () => {
+          expect(timeSeriesChart.vm.width).toBe(mockWidth);
+        });
+      });
+    });
+
+    describe('computed', () => {
+      describe('chartData', () => {
+        let chartData;
+        const seriesData = () => chartData[0];
+
+        beforeEach(() => {
+          ({ chartData } = timeSeriesChart.vm);
+        });
+
+        it('utilizes all data points', () => {
+          const { values } = mockGraphData.queries[0].result[0];
+
+          expect(chartData.length).toBe(1);
+          expect(seriesData().data.length).toBe(values.length);
+        });
+
+        it('creates valid data', () => {
+          const { data } = seriesData();
+
+          expect(
+            data.filter(
+              ([time, value]) => new Date(time).getTime() > 0 && typeof value === 'number',
+            ).length,
+          ).toBe(data.length);
+        });
+
+        it('formats line width correctly', () => {
+          expect(chartData[0].lineStyle.width).toBe(2);
+        });
+      });
+
+      describe('chartOptions', () => {
+        describe('yAxis formatter', () => {
+          let format;
+
+          beforeEach(() => {
+            format = timeSeriesChart.vm.chartOptions.yAxis.axisLabel.formatter;
+          });
+
+          it('rounds to 3 decimal places', () => {
+            expect(format(0.88888)).toBe('0.889');
+          });
+        });
+      });
+
+      describe('scatterSeries', () => {
+        it('utilizes deployment data', () => {
+          expect(timeSeriesChart.vm.scatterSeries.data).toEqual([
+            ['2017-05-31T21:23:37.881Z', 0],
+            ['2017-05-30T20:08:04.629Z', 0],
+            ['2017-05-30T17:42:38.409Z', 0],
+          ]);
+
+          expect(timeSeriesChart.vm.scatterSeries.symbolSize).toBe(14);
+        });
+      });
+
+      describe('yAxisLabel', () => {
+        it('constructs a label for the chart y-axis', () => {
+          expect(timeSeriesChart.vm.yAxisLabel).toBe('CPU');
+        });
+      });
+
+      describe('csvText', () => {
+        it('converts data from json to csv', () => {
+          const header = `timestamp,${mockGraphData.y_label}`;
+          const data = mockGraphData.queries[0].result[0].values;
+          const firstRow = `${data[0][0]},${data[0][1]}`;
+
+          expect(timeSeriesChart.vm.csvText).toMatch(`^${header}\r\n${firstRow}`);
+        });
+      });
+
+      describe('downloadLink', () => {
+        it('produces a link to download metrics as csv', () => {
+          const link = timeSeriesChart.vm.downloadLink;
+
+          expect(link).toContain('blob:');
+        });
+      });
+    });
+
+    afterEach(() => {
+      timeSeriesChart.destroy();
+    });
+  });
+
+  describe('wrapped components', () => {
+    const glChartComponents = [
+      {
+        chartType: 'area-chart',
+        component: GlAreaChart,
+      },
+      {
+        chartType: 'line-chart',
+        component: GlLineChart,
+      },
+    ];
+
+    glChartComponents.forEach(dynamicComponent => {
+      describe(`GitLab UI: ${dynamicComponent.chartType}`, () => {
+        let timeSeriesAreaChart;
+        let glChart;
+
+        beforeEach(done => {
+          timeSeriesAreaChart = makeTimeSeriesChart(mockGraphData, dynamicComponent.chartType);
+          glChart = timeSeriesAreaChart.find(dynamicComponent.component);
+          timeSeriesAreaChart.vm.$nextTick(done);
+        });
+
+        it('is a Vue instance', () => {
+          expect(glChart.exists()).toBe(true);
+          expect(glChart.isVueInstance()).toBe(true);
+        });
+
+        it('receives data properties needed for proper chart render', () => {
+          const props = glChart.props();
+
+          expect(props.data).toBe(timeSeriesAreaChart.vm.chartData);
+          expect(props.option).toBe(timeSeriesAreaChart.vm.chartOptions);
+          expect(props.formatTooltipText).toBe(timeSeriesAreaChart.vm.formatTooltipText);
+          expect(props.thresholds).toBe(timeSeriesAreaChart.vm.thresholds);
+        });
+
+        it('recieves a tooltip title', done => {
+          const mockTitle = 'mockTitle';
+          timeSeriesAreaChart.vm.tooltip.title = mockTitle;
+
+          timeSeriesAreaChart.vm.$nextTick(() => {
+            expect(shallowWrapperContainsSlotText(glChart, 'tooltipTitle', mockTitle)).toBe(true);
+            done();
+          });
+        });
+
+        describe('when tooltip is showing deployment data', () => {
+          beforeEach(done => {
+            timeSeriesAreaChart.vm.tooltip.isDeployment = true;
+            timeSeriesAreaChart.vm.$nextTick(done);
+          });
+
+          it('uses deployment title', () => {
+            expect(shallowWrapperContainsSlotText(glChart, 'tooltipTitle', 'Deployed')).toBe(true);
+          });
+
+          it('renders clickable commit sha in tooltip content', done => {
+            timeSeriesAreaChart.vm.tooltip.sha = mockSha;
+            timeSeriesAreaChart.vm.tooltip.commitUrl = commitUrl;
+
+            timeSeriesAreaChart.vm.$nextTick(() => {
+              const commitLink = timeSeriesAreaChart.find(GlLink);
+
+              expect(shallowWrapperContainsSlotText(commitLink, 'default', mockSha)).toBe(true);
+              expect(commitLink.attributes('href')).toEqual(commitUrl);
+              done();
+            });
+          });
+        });
+      });
+    });
+  });
+});
diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js
index 85e660d39257055c855e38c8ee98fd0059929b92..17e7314e214cf7453f84fd160ae6f494d6a608f3 100644
--- a/spec/javascripts/monitoring/mock_data.js
+++ b/spec/javascripts/monitoring/mock_data.js
@@ -1,5 +1,7 @@
 export const mockApiEndpoint = `${gl.TEST_HOST}/monitoring/mock`;
 
+export const mockProjectPath = '/frontend-fixtures/environments-project';
+
 export const metricsGroupsAPIResponse = {
   success: true,
   data: [
@@ -902,7 +904,7 @@ export const metricsDashboardResponse = {
           },
           {
             title: 'Memory Usage (Pod average)',
-            type: 'area-chart',
+            type: 'line-chart',
             y_label: 'Memory Used per Pod',
             weight: 2,
             metrics: [