Commit d2f99986 authored by Martin Wortschack's avatar Martin Wortschack

Merge branch 'design-repo-base' into 'master'

Design Repo Sync Status - Base View

See merge request gitlab-org/gitlab!19976
parents 3cb5e9e0 eda4a71a
<script> <script>
import { mapActions, mapState } from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import GeoDesigns from './geo_designs.vue';
import GeoDesignsEmptyState from './geo_designs_empty_state.vue';
import GeoDesignsDisabled from './geo_designs_disabled.vue'; import GeoDesignsDisabled from './geo_designs_disabled.vue';
export default { export default {
name: 'GeoDesignsApp', name: 'GeoDesignsApp',
components: { components: {
GlLoadingIcon,
GeoDesigns,
GeoDesignsEmptyState,
GeoDesignsDisabled, GeoDesignsDisabled,
}, },
mixins: [glFeatureFlagsMixin()],
props: { props: {
geoSvgPath: { geoSvgPath: {
type: String, type: String,
required: true, required: true,
}, },
issuesSvgPath: {
type: String,
required: true,
},
geoTroubleshootingLink: { geoTroubleshootingLink: {
type: String, type: String,
required: true, required: true,
...@@ -19,17 +32,40 @@ export default { ...@@ -19,17 +32,40 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
designsEnabled: { },
type: Boolean, data() {
required: true, return {
designsEnabled: Boolean(this.glFeatures.enableGeoDesignSync),
};
},
computed: {
...mapState(['isLoading', 'totalDesigns']),
hasDesigns() {
return this.totalDesigns > 0;
}, },
}, },
created() {
this.fetchDesigns();
},
methods: {
...mapActions(['fetchDesigns']),
},
}; };
</script> </script>
<template> <template>
<article class="geo-designs-container"> <article class="geo-designs-container">
<h2 v-if="designsEnabled">{{ __('Designs coming soon.') }}</h2> <section v-if="designsEnabled">
<gl-loading-icon v-if="isLoading" size="xl" />
<template v-else>
<geo-designs v-if="hasDesigns" />
<geo-designs-empty-state
v-else
:issues-svg-path="issuesSvgPath"
:geo-troubleshooting-link="geoTroubleshootingLink"
/>
</template>
</section>
<geo-designs-disabled <geo-designs-disabled
v-else v-else
:geo-svg-path="geoSvgPath" :geo-svg-path="geoSvgPath"
......
<script>
import { GlLink } from '@gitlab/ui';
import { __ } from '~/locale';
import GeoDesignStatus from './geo_design_status.vue';
import GeoDesignTimeAgo from './geo_design_time_ago.vue';
export default {
name: 'GeoDesign',
components: {
GlLink,
GeoDesignTimeAgo,
GeoDesignStatus,
},
props: {
name: {
type: String,
required: true,
},
projectId: {
type: Number,
required: true,
},
syncStatus: {
type: String,
required: false,
default: '',
},
lastSynced: {
type: String,
required: false,
default: '',
},
lastVerified: {
type: String,
required: false,
default: '',
},
lastChecked: {
type: String,
required: false,
default: '',
},
},
data() {
return {
timeAgoArray: [
{
label: __('Last successful sync'),
dateString: this.lastSynced,
defaultText: __('Never'),
},
{
label: __('Last time verified'),
dateString: this.lastVerified,
defaultText: __('Not Implemented'),
},
{
label: __('Last repository check run'),
dateString: this.lastChecked,
defaultText: __('Not Implemented'),
},
],
};
},
};
</script>
<template>
<div class="card">
<div class="card-header d-flex align-center">
<gl-link class="font-weight-bold" :href="`/${name}`" target="_blank">{{ name }}</gl-link>
</div>
<div class="card-body">
<div class="d-flex flex-column flex-md-row">
<div class="flex-grow-1">
<label class="text-muted">{{ __('Status') }}</label>
<geo-design-status :status="syncStatus" />
</div>
<geo-design-time-ago
v-for="(timeAgo, index) in timeAgoArray"
:key="index"
class="flex-grow-1"
:label="timeAgo.label"
:date-string="timeAgo.dateString"
:default-text="timeAgo.defaultText"
/>
</div>
</div>
</div>
</template>
<script>
import { STATUS_ICON_NAMES, STATUS_ICON_CLASS, DEFAULT_STATUS } from '../store/constants';
import Icon from '~/vue_shared/components/icon.vue';
export default {
name: 'GeoDesignStatus',
components: {
Icon,
},
props: {
status: {
type: String,
required: false,
default: DEFAULT_STATUS,
},
},
data() {
return {
icon: this.iconProperties(),
};
},
methods: {
iconProperties() {
if (STATUS_ICON_NAMES[this.status] && STATUS_ICON_CLASS[this.status]) {
return {
name: STATUS_ICON_NAMES[this.status],
cssClass: STATUS_ICON_CLASS[this.status],
};
}
return {
name: STATUS_ICON_NAMES[DEFAULT_STATUS],
cssClass: STATUS_ICON_CLASS[DEFAULT_STATUS],
};
},
},
};
</script>
<template>
<div>
<span class="d-flex align-items-center text-capitalize">
<icon :name="icon.name" :class="icon.cssClass" class="mr-2" />
{{ status }}
</span>
</div>
</template>
<script>
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
export default {
name: 'GeoDesignTimeAgo',
components: {
TimeAgo,
},
props: {
label: {
type: String,
required: true,
},
defaultText: {
type: String,
required: true,
},
dateString: {
type: String,
required: false,
default: '',
},
},
};
</script>
<template>
<div class="d-flex flex-column">
<label class="text-muted">{{ label }}</label>
<time-ago v-if="dateString" :time="dateString" tooltip-placement="bottom" />
<span v-else>{{ defaultText }}</span>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex';
import { GlPagination } from '@gitlab/ui';
import GeoDesign from './geo_design.vue';
export default {
name: 'GeoDesigns',
components: {
GlPagination,
GeoDesign,
},
computed: {
...mapState(['designs', 'currentPage', 'pageSize', 'totalDesigns']),
page: {
get() {
return this.currentPage;
},
set(newVal) {
this.setPage(newVal);
this.fetchDesigns();
},
},
hasDesigns() {
return this.totalDesigns > 0;
},
},
methods: {
...mapActions(['setPage', 'fetchDesigns']),
},
};
</script>
<template>
<section>
<geo-design
v-for="design in designs"
:key="design.id"
:name="design.name"
:project-id="design.project_id"
:sync-status="design.state"
:last-synced="design.last_synced_at"
:last-verified="design.last_verified_at"
:last-checked="design.last_checked_at"
/>
<gl-pagination
v-if="hasDesigns"
v-model="page"
:per-page="pageSize"
:total-items="totalDesigns"
align="center"
/>
</section>
</template>
<script>
import { GlEmptyState } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
export default {
name: 'GeoEmptyState',
components: {
GlEmptyState,
},
props: {
issuesSvgPath: {
type: String,
required: true,
},
geoTroubleshootingLink: {
type: String,
required: true,
},
},
computed: {
linkText() {
return sprintf(
s__(
'If you believe this may be an error, please refer to the %{linkStart}Geo Troubleshooting%{linkEnd} documentation for more information.',
),
{
linkStart: `<a href="${this.geoTroubleshootingLink}" target="_blank">`,
linkEnd: '</a>',
},
false,
);
},
},
};
</script>
<template>
<gl-empty-state :title="__('No Design Repositories match this filter')" :svg-path="issuesSvgPath">
<template v-slot:description>
<div class="text-center">
<p>{{ __('Adjust your filters/search criteria above.') }}</p>
<p v-html="linkText"></p>
</div>
</template>
</gl-empty-state>
</template>
...@@ -16,14 +16,14 @@ export default () => { ...@@ -16,14 +16,14 @@ export default () => {
}, },
data() { data() {
const { const {
dataset: { geoSvgPath, geoTroubleshootingLink, designManagementLink, designsEnabled }, dataset: { geoSvgPath, issuesSvgPath, geoTroubleshootingLink, designManagementLink },
} = this.$options.el; } = this.$options.el;
return { return {
geoSvgPath, geoSvgPath,
issuesSvgPath,
geoTroubleshootingLink, geoTroubleshootingLink,
designManagementLink, designManagementLink,
designsEnabled,
}; };
}, },
...@@ -31,9 +31,9 @@ export default () => { ...@@ -31,9 +31,9 @@ export default () => {
return createElement('geo-designs-app', { return createElement('geo-designs-app', {
props: { props: {
geoSvgPath: this.geoSvgPath, geoSvgPath: this.geoSvgPath,
issuesSvgPath: this.issuesSvgPath,
geoTroubleshootingLink: this.geoTroubleshootingLink, geoTroubleshootingLink: this.geoTroubleshootingLink,
designManagementLink: this.designManagementLink, designManagementLink: this.designManagementLink,
designsEnabled: this.designsEnabled,
}, },
}); });
}, },
......
// eslint-disable-next-line import/prefer-default-export
export const FILTER_STATES = { export const FILTER_STATES = {
ALL: 'all', ALL: 'all',
SYNCED: 'synced', SYNCED: 'synced',
PENDING: 'pending', PENDING: 'pending',
FAILED: 'failed', FAILED: 'failed',
}; };
export const DEFAULT_STATUS = 'never';
export const STATUS_ICON_NAMES = {
[FILTER_STATES.SYNCED]: 'status_closed',
[FILTER_STATES.PENDING]: 'status_scheduled',
[FILTER_STATES.FAILED]: 'status_failed',
[DEFAULT_STATUS]: 'status_notfound',
};
export const STATUS_ICON_CLASS = {
[FILTER_STATES.SYNCED]: 'text-success',
[FILTER_STATES.PENDING]: 'text-warning',
[FILTER_STATES.FAILED]: 'text-danger',
[DEFAULT_STATUS]: 'text-muted',
};
...@@ -3,6 +3,10 @@ ...@@ -3,6 +3,10 @@
class Admin::Geo::DesignsController < Admin::Geo::ApplicationController class Admin::Geo::DesignsController < Admin::Geo::ApplicationController
before_action :check_license! before_action :check_license!
before_action do
push_frontend_feature_flag(:enable_geo_design_sync)
end
def index def index
end end
end end
...@@ -2,6 +2,6 @@ ...@@ -2,6 +2,6 @@
- @content_class = "geo-admin-container" - @content_class = "geo-admin-container"
#js-geo-designs{ data: { "geo-svg-path" => image_path('illustrations/gitlab_geo.svg'), #js-geo-designs{ data: { "geo-svg-path" => image_path('illustrations/gitlab_geo.svg'),
"issues-svg-path" => image_path('illustrations/issues.svg'),
"geo-troubleshooting-link" => help_page_path('administration/geo/replication/troubleshooting.html'), "geo-troubleshooting-link" => help_page_path('administration/geo/replication/troubleshooting.html'),
"design-management-link" => help_page_path('user/project/issues/design_management.html'), "design-management-link" => help_page_path('user/project/issues/design_management.html') } }
"designs-enabled" => Feature.enabled?(:enable_geo_design_sync) && Feature.enabled?(:enable_geo_design_view) } }
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
= link_to admin_geo_projects_path, title: 'Projects' do = link_to admin_geo_projects_path, title: 'Projects' do
%span %span
= _('Projects') = _('Projects')
- if Feature.enabled?(:enable_geo_design_sync) && Feature.enabled?(:enable_geo_design_view) - if Feature.enabled?(:enable_geo_design_sync)
= nav_link(path: 'admin/geo/designs#index') do = nav_link(path: 'admin/geo/designs#index') do
= link_to admin_geo_designs_path, title: _('Designs') do = link_to admin_geo_designs_path, title: _('Designs') do
%span %span
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`GeoDesignTimeAgo template when dateString exists TimeAgo sets innerHTML as 09-23-1994 1`] = `"<time title=\\"\\" class=\\"\\" data-original-title=\\"Sep 23, 1994 12:00am GMT+0000\\">25 years ago</time>"`;
exports[`GeoDesignTimeAgo template when dateString is null DefaultText sets innerHTML as props.defaultText 1`] = `"<span>Default Text</span>"`;
import Vuex from 'vuex'; import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
import GeoDesignsApp from 'ee/geo_designs/components/app.vue'; import GeoDesignsApp from 'ee/geo_designs/components/app.vue';
import store from 'ee/geo_designs/store'; import store from 'ee/geo_designs/store';
import GeoDesignsDisabled from 'ee/geo_designs/components/geo_designs_disabled.vue'; import GeoDesignsDisabled from 'ee/geo_designs/components/geo_designs_disabled.vue';
import GeoDesigns from 'ee/geo_designs/components/geo_designs.vue';
import GeoDesignsEmptyState from 'ee/geo_designs/components/geo_designs_empty_state.vue';
import { import {
MOCK_GEO_SVG_PATH, MOCK_GEO_SVG_PATH,
MOCK_ISSUES_SVG_PATH,
MOCK_GEO_TROUBLESHOOTING_LINK, MOCK_GEO_TROUBLESHOOTING_LINK,
MOCK_DESIGN_MANAGEMENT_LINK, MOCK_DESIGN_MANAGEMENT_LINK,
MOCK_BASIC_FETCH_DATA_MAP,
} from '../mock_data'; } from '../mock_data';
const localVue = createLocalVue(); const localVue = createLocalVue();
...@@ -17,9 +22,18 @@ describe('GeoDesignsApp', () => { ...@@ -17,9 +22,18 @@ describe('GeoDesignsApp', () => {
const propsData = { const propsData = {
geoSvgPath: MOCK_GEO_SVG_PATH, geoSvgPath: MOCK_GEO_SVG_PATH,
issuesSvgPath: MOCK_ISSUES_SVG_PATH,
geoTroubleshootingLink: MOCK_GEO_TROUBLESHOOTING_LINK, geoTroubleshootingLink: MOCK_GEO_TROUBLESHOOTING_LINK,
designManagementLink: MOCK_DESIGN_MANAGEMENT_LINK, designManagementLink: MOCK_DESIGN_MANAGEMENT_LINK,
designsEnabled: true, };
const actionSpies = {
fetchDesigns: jest.fn(),
setEndpoint: jest.fn(),
};
const glFeatures = {
enableGeoDesignSync: true,
}; };
const createComponent = () => { const createComponent = () => {
...@@ -27,6 +41,12 @@ describe('GeoDesignsApp', () => { ...@@ -27,6 +41,12 @@ describe('GeoDesignsApp', () => {
localVue, localVue,
store, store,
propsData, propsData,
methods: {
...actionSpies,
},
provide: {
glFeatures,
},
}); });
}; };
...@@ -35,8 +55,11 @@ describe('GeoDesignsApp', () => { ...@@ -35,8 +55,11 @@ describe('GeoDesignsApp', () => {
}); });
const findGeoDesignsContainer = () => wrapper.find('.geo-designs-container'); const findGeoDesignsContainer = () => wrapper.find('.geo-designs-container');
const findDesignsComingSoon = () => findGeoDesignsContainer().find('h2'); const findGeoDesignsEnabledContainer = () => findGeoDesignsContainer().find('section');
const findGeoDesignsDisabled = () => findGeoDesignsContainer().find(GeoDesignsDisabled); const findGeoDesignsDisabled = () => findGeoDesignsContainer().find(GeoDesignsDisabled);
const findGlLoadingIcon = () => findGeoDesignsContainer().find(GlLoadingIcon);
const findGeoDesigns = () => findGeoDesignsContainer().find(GeoDesigns);
const findGeoDesignsEmptyState = () => findGeoDesignsContainer().find(GeoDesignsEmptyState);
describe('template', () => { describe('template', () => {
beforeEach(() => { beforeEach(() => {
...@@ -49,32 +72,104 @@ describe('GeoDesignsApp', () => { ...@@ -49,32 +72,104 @@ describe('GeoDesignsApp', () => {
describe('when designsEnabled = false', () => { describe('when designsEnabled = false', () => {
beforeEach(() => { beforeEach(() => {
propsData.designsEnabled = false; glFeatures.enableGeoDesignSync = false;
createComponent(); createComponent();
}); });
it('hides designs coming soon text', () => {
expect(findDesignsComingSoon().exists()).toBe(false);
});
it('shows designs disabled component', () => { it('shows designs disabled component', () => {
expect(findGeoDesignsDisabled().exists()).toBe(true); expect(findGeoDesignsDisabled().exists()).toBe(true);
}); });
it('hides designs enabled container', () => {
expect(findGeoDesignsEnabledContainer().exists()).toBe(false);
});
}); });
describe('when designsEnabled = true', () => { describe('when designsEnabled = true', () => {
beforeEach(() => { beforeEach(() => {
propsData.designsEnabled = true; glFeatures.enableGeoDesignSync = true;
createComponent(); createComponent();
}); });
it('shows designs coming soon text', () => {
expect(findDesignsComingSoon().exists()).toBe(true);
});
it('hides designs disabled component', () => { it('hides designs disabled component', () => {
expect(findGeoDesignsDisabled().exists()).toBe(false); expect(findGeoDesignsDisabled().exists()).toBe(false);
}); });
it('shows designs enabled container', () => {
expect(findGeoDesignsEnabledContainer().exists()).toBe(true);
});
describe('when isLoading = true', () => {
beforeEach(() => {
wrapper.vm.$store.state.isLoading = true;
});
it('hides designs', () => {
expect(findGeoDesigns().exists()).toBe(false);
});
it('hides empty state', () => {
expect(findGeoDesignsEmptyState().exists()).toBe(false);
});
it('shows loader', () => {
expect(findGlLoadingIcon().exists()).toBe(true);
});
});
describe('when isLoading = false', () => {
beforeEach(() => {
wrapper.vm.$store.state.isLoading = false;
});
describe('with designs', () => {
beforeEach(() => {
wrapper.vm.$store.state.designs = MOCK_BASIC_FETCH_DATA_MAP.data;
wrapper.vm.$store.state.totalDesigns = MOCK_BASIC_FETCH_DATA_MAP.total;
});
it('shows designs', () => {
expect(findGeoDesigns().exists()).toBe(true);
});
it('hides empty state', () => {
expect(findGeoDesignsEmptyState().exists()).toBe(false);
});
it('hides loader', () => {
expect(findGlLoadingIcon().exists()).toBe(false);
});
});
describe('with no designs', () => {
beforeEach(() => {
wrapper.vm.$store.state.designs = [];
wrapper.vm.$store.state.totalDesigns = 0;
});
it('hides designs', () => {
expect(findGeoDesigns().exists()).toBe(false);
});
it('shows empty state', () => {
expect(findGeoDesignsEmptyState().exists()).toBe(true);
});
it('hides loader', () => {
expect(findGlLoadingIcon().exists()).toBe(false);
});
});
});
});
});
describe('onCreate', () => {
beforeEach(() => {
createComponent();
});
it('calls fetchDesigns', () => {
expect(actionSpies.fetchDesigns).toHaveBeenCalled();
}); });
}); });
}); });
import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
import GeoDesign from 'ee/geo_designs/components/geo_design.vue';
import store from 'ee/geo_designs/store';
import { MOCK_BASIC_FETCH_DATA_MAP } from '../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('GeoDesignsApp', () => {
let wrapper;
const mockDesign = MOCK_BASIC_FETCH_DATA_MAP.data[0];
const propsData = {
name: mockDesign.name,
projectId: mockDesign.project_id,
syncStatus: mockDesign.state,
lastSynced: mockDesign.last_synced_at,
lastVerified: null,
lastChecked: null,
};
const createComponent = () => {
wrapper = shallowMount(localVue.extend(GeoDesign), {
localVue,
store,
propsData,
});
};
afterEach(() => {
wrapper.destroy();
});
const findCard = () => wrapper.find('.card');
const findGlLink = () => findCard().find(GlLink);
const findCardHeader = () => findCard().find('.card-header');
const findCardBody = () => findCard().find('.card-body');
describe('template', () => {
beforeEach(() => {
createComponent();
});
it('renders card', () => {
expect(findCard().exists()).toBe(true);
});
it('renders card header', () => {
expect(findCardHeader().exists()).toBe(true);
});
it('renders card body', () => {
expect(findCardBody().exists()).toBe(true);
});
it('GlLink renders', () => {
expect(findGlLink().exists()).toBe(true);
});
});
});
import Vuex from 'vuex';
import { createLocalVue, mount } from '@vue/test-utils';
import Icon from '~/vue_shared/components/icon.vue';
import store from 'ee/geo_designs/store';
import GeoDesignStatus from 'ee/geo_designs/components/geo_design_status.vue';
import {
FILTER_STATES,
STATUS_ICON_NAMES,
STATUS_ICON_CLASS,
DEFAULT_STATUS,
} from 'ee/geo_designs/store/constants';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('GeoDesignStatus', () => {
let wrapper;
const propsData = {
status: FILTER_STATES.SYNCED,
};
const createComponent = () => {
wrapper = mount(localVue.extend(GeoDesignStatus), {
localVue,
store,
propsData,
});
};
afterEach(() => {
wrapper.destroy();
});
const findGeoDesignStatusContainer = () => wrapper.find('div');
const findIcon = () => findGeoDesignStatusContainer().find(Icon);
describe('template', () => {
beforeEach(() => {
createComponent();
});
it('renders status container', () => {
expect(findGeoDesignStatusContainer().exists()).toBe(true);
});
});
describe.each`
status | iconName | iconClass
${FILTER_STATES.SYNCED} | ${STATUS_ICON_NAMES[FILTER_STATES.SYNCED]} | ${STATUS_ICON_CLASS[FILTER_STATES.SYNCED]}
${FILTER_STATES.PENDING} | ${STATUS_ICON_NAMES[FILTER_STATES.PENDING]} | ${STATUS_ICON_CLASS[FILTER_STATES.PENDING]}
${FILTER_STATES.FAILED} | ${STATUS_ICON_NAMES[FILTER_STATES.FAILED]} | ${STATUS_ICON_CLASS[FILTER_STATES.FAILED]}
${DEFAULT_STATUS} | ${STATUS_ICON_NAMES[DEFAULT_STATUS]} | ${STATUS_ICON_CLASS[DEFAULT_STATUS]}
`(`iconProperties`, ({ status, iconName, iconClass }) => {
beforeEach(() => {
propsData.status = status;
createComponent();
});
describe(`with filter set to ${status}`, () => {
beforeEach(() => {
wrapper.vm.icon = wrapper.vm.iconProperties();
});
it(`sets icon.name to ${iconName}`, () => {
expect(wrapper.vm.icon.name).toEqual(iconName);
});
it(`sets icon.cssClass to ${iconClass}`, () => {
expect(wrapper.vm.icon.cssClass).toEqual(iconClass);
});
it(`sets svg to ic-${iconName}`, () => {
expect(findIcon().classes()).toContain(`ic-${wrapper.vm.icon.name}`);
});
});
});
});
import Vuex from 'vuex';
import { createLocalVue, mount } from '@vue/test-utils';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import store from 'ee/geo_designs/store';
import GeoDesignTimeAgo from 'ee/geo_designs/components/geo_design_time_ago.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('GeoDesignTimeAgo', () => {
let wrapper;
const propsData = {
label: 'Test Label',
dateString: '09-23-1994',
defaultText: 'Default Text',
};
const createComponent = () => {
wrapper = mount(localVue.extend(GeoDesignTimeAgo), {
localVue,
store,
propsData,
});
};
afterEach(() => {
wrapper.destroy();
});
const findGeoDesignTimeAgo = () => wrapper.find(GeoDesignTimeAgo);
const findTimeAgo = () => findGeoDesignTimeAgo().find(TimeAgo);
const findDefaultText = () => findGeoDesignTimeAgo().find('span');
describe('template', () => {
beforeEach(() => {
createComponent();
});
it('renders GeoDesignTimeAgo container', () => {
expect(findGeoDesignTimeAgo().exists()).toBe(true);
});
describe('when dateString exists', () => {
describe('TimeAgo', () => {
it('renders', () => {
expect(findTimeAgo().exists()).toBe(true);
});
it('sets time prop', () => {
expect(findTimeAgo().props().time).toBe(propsData.dateString);
});
it(`sets innerHTML as ${propsData.dateString}`, () => {
expect(findTimeAgo().html()).toMatchSnapshot();
});
});
it('hides DefaultText', () => {
expect(findDefaultText().exists()).toBe(false);
});
});
describe('when dateString is null', () => {
beforeEach(() => {
propsData.dateString = null;
createComponent();
});
it('hides TimeAgo', () => {
expect(findTimeAgo().exists()).toBe(false);
});
describe('DefaultText', () => {
it('renders', () => {
expect(findDefaultText().exists()).toBe(true);
});
it('sets innerHTML as props.defaultText', () => {
expect(findDefaultText().html()).toMatchSnapshot();
});
});
});
});
});
import Vuex from 'vuex';
import { createLocalVue, mount } from '@vue/test-utils';
import { GlEmptyState } from '@gitlab/ui';
import store from 'ee/geo_designs/store';
import GeoDesignsEmptyState from 'ee/geo_designs/components/geo_designs_empty_state.vue';
import { MOCK_ISSUES_SVG_PATH, MOCK_GEO_TROUBLESHOOTING_LINK } from '../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('GeoDesignsEmptyState', () => {
let wrapper;
const propsData = {
issuesSvgPath: MOCK_ISSUES_SVG_PATH,
geoTroubleshootingLink: MOCK_GEO_TROUBLESHOOTING_LINK,
};
const createComponent = () => {
wrapper = mount(localVue.extend(GeoDesignsEmptyState), {
localVue,
store,
propsData,
});
};
afterEach(() => {
wrapper.destroy();
});
const findGlEmptyState = () => wrapper.find(GlEmptyState);
const findLink = () => findGlEmptyState().find('a');
describe('template', () => {
beforeEach(() => {
createComponent();
});
it('renders GlEmptyState', () => {
expect(findGlEmptyState().exists()).toBe(true);
});
it('Link renders', () => {
expect(findLink().exists()).toBe(true);
});
});
});
import Vuex from 'vuex';
import { createLocalVue, mount } from '@vue/test-utils';
import { GlPagination } from '@gitlab/ui';
import store from 'ee/geo_designs/store';
import GeoDesigns from 'ee/geo_designs/components/geo_designs.vue';
import GeoDesign from 'ee/geo_designs/components/geo_design.vue';
import { MOCK_BASIC_FETCH_DATA_MAP } from '../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('GeoDesigns', () => {
let wrapper;
const actionSpies = {
setPage: jest.fn(),
fetchDesigns: jest.fn(),
};
const createComponent = () => {
wrapper = mount(localVue.extend(GeoDesigns), {
localVue,
store,
methods: {
...actionSpies,
},
});
};
afterEach(() => {
wrapper.destroy();
});
const findGeoDesignsContainer = () => wrapper.find('section');
const findGlPagination = () => findGeoDesignsContainer().find(GlPagination);
const findGeoDesign = () => findGeoDesignsContainer().findAll(GeoDesign);
describe('template', () => {
beforeEach(() => {
createComponent();
});
it('renders the designs container', () => {
expect(findGeoDesignsContainer().exists()).toBe(true);
});
describe('GlPagination', () => {
describe('when pageSize >= totalDesigns', () => {
beforeEach(() => {
wrapper.vm.$store.state.pageSize = 2;
wrapper.vm.$store.state.totalDesigns = 1;
});
it('is hidden', () => {
expect(findGlPagination().html()).toBeUndefined();
});
});
describe('when pageSize < totalDesigns', () => {
beforeEach(() => {
wrapper.vm.$store.state.pageSize = 1;
wrapper.vm.$store.state.totalDesigns = 2;
});
it('renders', () => {
expect(findGlPagination().html()).not.toBeUndefined();
});
});
});
describe('GeoDesign', () => {
beforeEach(() => {
wrapper.vm.$store.state.designs = MOCK_BASIC_FETCH_DATA_MAP.data;
});
it('renders an instance for each design in the store', () => {
const designWrappers = findGeoDesign();
const designs = [...wrapper.vm.$store.state.designs];
for (let i = 0; i < designWrappers.length; i += 1) {
expect(designWrappers.at(i).props().projectId).toBe(designs[i].project_id);
}
});
});
});
describe('changing the page', () => {
beforeEach(() => {
createComponent();
wrapper.vm.page = 2;
});
it('should call setPage', () => {
expect(actionSpies.setPage).toHaveBeenCalledWith(2);
});
it('should call fetchDesigns', () => {
expect(actionSpies.fetchDesigns).toHaveBeenCalled();
});
});
});
export const MOCK_GEO_SVG_PATH = 'illustrations/gitlab_geo.svg'; export const MOCK_GEO_SVG_PATH = 'illustrations/gitlab_geo.svg';
export const MOCK_ISSUES_SVG_PATH = 'illustrations/issues.svg';
export const MOCK_GEO_TROUBLESHOOTING_LINK = export const MOCK_GEO_TROUBLESHOOTING_LINK =
'https://docs.gitlab.com/ee/administration/geo/replication/troubleshooting.html'; 'https://docs.gitlab.com/ee/administration/geo/replication/troubleshooting.html';
......
...@@ -1119,6 +1119,9 @@ msgstr "" ...@@ -1119,6 +1119,9 @@ msgstr ""
msgid "Adds an issue to an epic." msgid "Adds an issue to an epic."
msgstr "" msgstr ""
msgid "Adjust your filters/search criteria above."
msgstr ""
msgid "Admin Area" msgid "Admin Area"
msgstr "" msgstr ""
...@@ -5904,9 +5907,6 @@ msgstr "" ...@@ -5904,9 +5907,6 @@ msgstr ""
msgid "Designs" msgid "Designs"
msgstr "" msgstr ""
msgid "Designs coming soon."
msgstr ""
msgid "Destroy" msgid "Destroy"
msgstr "" msgstr ""
...@@ -9292,6 +9292,9 @@ msgstr "" ...@@ -9292,6 +9292,9 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}" msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr "" msgstr ""
msgid "If you believe this may be an error, please refer to the %{linkStart}Geo Troubleshooting%{linkEnd} documentation for more information."
msgstr ""
msgid "If you believe this page to be an error, check out the links below for more information." msgid "If you believe this page to be an error, check out the links below for more information."
msgstr "" msgstr ""
...@@ -10130,12 +10133,21 @@ msgstr "" ...@@ -10130,12 +10133,21 @@ msgstr ""
msgid "Last reply by" msgid "Last reply by"
msgstr "" msgstr ""
msgid "Last repository check run"
msgstr ""
msgid "Last seen" msgid "Last seen"
msgstr "" msgstr ""
msgid "Last successful sync"
msgstr ""
msgid "Last successful update" msgid "Last successful update"
msgstr "" msgstr ""
msgid "Last time verified"
msgstr ""
msgid "Last update" msgid "Last update"
msgstr "" msgstr ""
...@@ -11500,6 +11512,9 @@ msgstr "" ...@@ -11500,6 +11512,9 @@ msgstr ""
msgid "No %{providerTitle} repositories found" msgid "No %{providerTitle} repositories found"
msgstr "" msgstr ""
msgid "No Design Repositories match this filter"
msgstr ""
msgid "No Epic" msgid "No Epic"
msgstr "" msgstr ""
...@@ -11692,6 +11707,9 @@ msgstr "" ...@@ -11692,6 +11707,9 @@ msgstr ""
msgid "None" msgid "None"
msgstr "" msgstr ""
msgid "Not Implemented"
msgstr ""
msgid "Not all data has been processed yet, the accuracy of the chart for the selected timeframe is limited." msgid "Not all data has been processed yet, the accuracy of the chart for the selected timeframe is limited."
msgstr "" msgstr ""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment