summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/environments
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/environments')
-rw-r--r--app/assets/javascripts/environments/components/delete_environment_modal.vue6
-rw-r--r--app/assets/javascripts/environments/components/deployment.vue4
-rw-r--r--app/assets/javascripts/environments/components/edit_environment.vue4
-rw-r--r--app/assets/javascripts/environments/components/empty_state.vue45
-rw-r--r--app/assets/javascripts/environments/components/enable_review_app_modal.vue160
-rw-r--r--app/assets/javascripts/environments/components/environment_external_url.vue16
-rw-r--r--app/assets/javascripts/environments/components/environment_folder.vue10
-rw-r--r--app/assets/javascripts/environments/components/environments_app.vue60
-rw-r--r--app/assets/javascripts/environments/components/environments_detail_header.vue37
-rw-r--r--app/assets/javascripts/environments/components/new_environment.vue4
-rw-r--r--app/assets/javascripts/environments/constants.js31
-rw-r--r--app/assets/javascripts/environments/graphql/queries/environment_app.query.graphql4
-rw-r--r--app/assets/javascripts/environments/graphql/queries/folder.query.graphql4
-rw-r--r--app/assets/javascripts/environments/graphql/resolvers.js8
-rw-r--r--app/assets/javascripts/environments/mixins/environments_mixin.js8
15 files changed, 258 insertions, 143 deletions
diff --git a/app/assets/javascripts/environments/components/delete_environment_modal.vue b/app/assets/javascripts/environments/components/delete_environment_modal.vue
index 3173c2bd644..78e1b8d5cb2 100644
--- a/app/assets/javascripts/environments/components/delete_environment_modal.vue
+++ b/app/assets/javascripts/environments/components/delete_environment_modal.vue
@@ -1,6 +1,6 @@
<script>
import { GlTooltipDirective, GlModal } from '@gitlab/ui';
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import { __, s__, sprintf } from '~/locale';
import eventHub from '../event_hub';
import deleteEnvironmentMutation from '../graphql/mutations/delete_environment.mutation.graphql';
@@ -65,11 +65,11 @@ export default {
.then(({ data }) => {
const [message] = data?.deleteEvironment?.errors ?? [];
if (message) {
- createFlash({ message });
+ createAlert({ message });
}
})
.catch((error) =>
- createFlash({
+ createAlert({
message: s__(
'Environments|An error occurred while deleting the environment. Check if the environment stopped; if not, stop it and try again.',
),
diff --git a/app/assets/javascripts/environments/components/deployment.vue b/app/assets/javascripts/environments/components/deployment.vue
index 3475b38c8c9..b00a0777a03 100644
--- a/app/assets/javascripts/environments/components/deployment.vue
+++ b/app/assets/javascripts/environments/components/deployment.vue
@@ -10,7 +10,7 @@ import {
import { __, s__ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import deploymentDetails from '../graphql/queries/deployment_details.query.graphql';
import DeploymentStatusBadge from './deployment_status_badge.vue';
import Commit from './commit.vue';
@@ -119,7 +119,7 @@ export default {
return data?.project?.deployment?.tags;
},
error(error) {
- createFlash({
+ createAlert({
message: this.$options.i18n.LOAD_ERROR_MESSAGE,
captureError: true,
error,
diff --git a/app/assets/javascripts/environments/components/edit_environment.vue b/app/assets/javascripts/environments/components/edit_environment.vue
index 96742a11ebb..901d0f5b34d 100644
--- a/app/assets/javascripts/environments/components/edit_environment.vue
+++ b/app/assets/javascripts/environments/components/edit_environment.vue
@@ -1,5 +1,5 @@
<script>
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { visitUrl } from '~/lib/utils/url_utility';
import EnvironmentForm from './environment_form.vue';
@@ -39,7 +39,7 @@ export default {
.then(({ data: { path } }) => visitUrl(path))
.catch((error) => {
const message = error.response.data.message[0];
- createFlash({ message });
+ createAlert({ message });
this.loading = false;
});
},
diff --git a/app/assets/javascripts/environments/components/empty_state.vue b/app/assets/javascripts/environments/components/empty_state.vue
index 563fa6c96fb..e40c37b5095 100644
--- a/app/assets/javascripts/environments/components/empty_state.vue
+++ b/app/assets/javascripts/environments/components/empty_state.vue
@@ -1,9 +1,14 @@
<script>
+import { GlEmptyState, GlLink } from '@gitlab/ui';
import { s__ } from '~/locale';
import { ENVIRONMENTS_SCOPE } from '../constants';
export default {
- name: 'EnvironmentsEmptyState',
+ components: {
+ GlEmptyState,
+ GlLink,
+ },
+ inject: ['newEnvironmentPath'],
props: {
helpPath: {
type: String,
@@ -13,10 +18,23 @@ export default {
type: String,
required: true,
},
+ hasTerm: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
computed: {
title() {
- return this.$options.i18n.title[this.scope];
+ return this.hasTerm
+ ? this.$options.i18n.searchingTitle
+ : this.$options.i18n.title[this.scope];
+ },
+ content() {
+ return this.hasTerm ? this.$options.i18n.searchingContent : this.$options.i18n.content;
+ },
+ buttonText() {
+ return this.hasTerm ? this.$options.i18n.newEnvironmentButtonLabel : '';
},
},
i18n: {
@@ -27,20 +45,21 @@ export default {
content: s__(
'Environments|Environments are places where code gets deployed, such as staging or production.',
),
+ searchingTitle: s__('Environments|No results found'),
+ searchingContent: s__('Environments|Edit your search and try again'),
link: s__('Environments|How do I create an environment?'),
+ newEnvironmentButtonLabel: s__('Environments|New environment'),
},
};
</script>
<template>
- <div class="empty-state">
- <div class="text-content">
- <h4 class="js-blank-state-title">
- {{ title }}
- </h4>
- <p>
- {{ $options.i18n.content }}
- <a :href="helpPath"> {{ $options.i18n.link }} </a>
- </p>
- </div>
- </div>
+ <gl-empty-state :primary-button-text="buttonText" :primary-button-link="newEnvironmentPath">
+ <template #title>
+ <h4>{{ title }}</h4>
+ </template>
+ <template #description>
+ <p>{{ content }}</p>
+ <gl-link v-if="!hasTerm" :href="helpPath">{{ $options.i18n.link }}</gl-link>
+ </template>
+ </gl-empty-state>
</template>
diff --git a/app/assets/javascripts/environments/components/enable_review_app_modal.vue b/app/assets/javascripts/environments/components/enable_review_app_modal.vue
index 6343fe8702a..420ad3d9c42 100644
--- a/app/assets/javascripts/environments/components/enable_review_app_modal.vue
+++ b/app/assets/javascripts/environments/components/enable_review_app_modal.vue
@@ -1,18 +1,19 @@
<script>
-import { GlLink, GlModal, GlSprintf } from '@gitlab/ui';
+import { GlLink, GlModal, GlSprintf, GlIcon, GlPopover } from '@gitlab/ui';
import { uniqueId } from 'lodash';
import { helpPagePath } from '~/helpers/help_page_helper';
-import { s__ } from '~/locale';
import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
+import { REVIEW_APP_MODAL_I18N as i18n } from '../constants';
export default {
components: {
GlLink,
GlModal,
GlSprintf,
+ GlIcon,
+ GlPopover,
ModalCopyButton,
},
- inject: ['defaultBranchName'],
model: {
prop: 'visible',
event: 'change',
@@ -28,25 +29,6 @@ export default {
default: false,
},
},
- instructionText: {
- step1: s__(
- 'EnableReviewApp|%{stepStart}Step 1%{stepEnd}. Ensure you have Kubernetes set up and have a base domain for your %{linkStart}cluster%{linkEnd}.',
- ),
- step2: s__('EnableReviewApp|%{stepStart}Step 2%{stepEnd}. Copy the following snippet:'),
- step3: s__(
- `EnableReviewApp|%{stepStart}Step 3%{stepEnd}. Add it to the project %{linkStart}gitlab-ci.yml%{linkEnd} file.`,
- ),
- step4: s__(
- `EnableReviewApp|%{stepStart}Step 4 (optional)%{stepEnd}. Enable Visual Reviews by following the %{linkStart}setup instructions%{linkEnd}.`,
- ),
- },
- modalInfo: {
- closeText: s__('EnableReviewApp|Close'),
- copyToClipboardText: s__('EnableReviewApp|Copy snippet text'),
- title: s__('ReviewApp|Enable Review App'),
- },
- visualReviewsDocs: helpPagePath('ci/review_apps/index.md', { anchor: 'visual-reviews' }),
- connectClusterDocs: helpPagePath('user/clusters/agent/index'),
data() {
const modalInfoCopyId = uniqueId('enable-review-app-copy-string-');
@@ -57,81 +39,99 @@ export default {
return `deploy_review:
stage: deploy
script:
- - echo "Deploy a review app"
+ - echo "Add script here that deploys the code to your infrastructure"
environment:
name: review/$CI_COMMIT_REF_NAME
url: https://$CI_ENVIRONMENT_SLUG.example.com
- only:
- - branches
- except:
- - ${this.defaultBranchName}`;
+ rules:
+ - if: $CI_PIPELINE_SOURCE == "merge_request_event"`;
+ },
+ },
+ methods: {
+ commaOrPeriod(index, length) {
+ return index + 1 === length ? '.' : ',';
},
},
+ i18n,
+ configuringReviewAppsPath: helpPagePath('ci/review_apps/index.md', {
+ anchor: 'configuring-review-apps',
+ }),
+ reviewAppsExamplesPath: helpPagePath('ci/review_apps/index.md', {
+ anchor: 'review-apps-examples',
+ }),
};
</script>
<template>
<gl-modal
:visible="visible"
:modal-id="modalId"
- :title="$options.modalInfo.title"
+ :title="$options.i18n.title"
static
size="lg"
- ok-only
- ok-variant="light"
- :ok-title="$options.modalInfo.closeText"
+ hide-footer
@change="$emit('change', $event)"
>
+ <p>{{ $options.i18n.intro }}</p>
<p>
- <gl-sprintf :message="$options.instructionText.step1">
- <template #step="{ content }">
- <strong>{{ content }}</strong>
- </template>
- <template #link="{ content }">
- <gl-link :href="$options.connectClusterDocs" target="_blank">{{ content }}</gl-link>
- </template>
- </gl-sprintf>
+ <strong>{{ $options.i18n.instructions.title }}</strong>
</p>
- <div>
- <p>
- <gl-sprintf :message="$options.instructionText.step2">
- <template #step="{ content }">
- <strong>{{ content }}</strong>
- </template>
- </gl-sprintf>
- </p>
- <div class="gl-display-flex align-items-start">
- <pre :id="modalInfoCopyId" class="gl-w-full" data-testid="enable-review-app-copy-string">
- {{ modalInfoCopyStr }} </pre
- >
- <modal-copy-button
- :title="$options.modalInfo.copyToClipboardText"
- :modal-id="modalId"
- css-classes="border-0"
- :target="`#${modalInfoCopyId}`"
- />
- </div>
+ <div class="gl-mb-6">
+ <ol class="gl-px-6">
+ <li>
+ {{ $options.i18n.instructions.step1 }}
+ <gl-icon
+ ref="informationIcon"
+ name="information-o"
+ class="gl-text-blue-600 gl-hover-cursor-pointer"
+ />
+ <gl-popover
+ :target="() => $refs.informationIcon.$el"
+ :title="$options.i18n.staticSitePopover.title"
+ triggers="hover focus"
+ >
+ {{ $options.i18n.staticSitePopover.body }}
+ </gl-popover>
+ </li>
+ <li>{{ $options.i18n.instructions.step2 }}</li>
+ <li>
+ {{ $options.i18n.instructions.step3 }}
+ <ul class="gl-px-4 gl-py-2">
+ <li>{{ $options.i18n.instructions.step3a }}</li>
+ <li>
+ <gl-sprintf :message="$options.i18n.instructions.step3b">
+ <template #code="{ content }"
+ ><code>{{ content }}</code></template
+ >
+ </gl-sprintf>
+ </li>
+ <li class="gl-list-style-none">
+ <div class="gl-display-flex align-items-start">
+ <pre
+ :id="modalInfoCopyId"
+ class="gl-w-full"
+ data-testid="enable-review-app-copy-string"
+ >{{ modalInfoCopyStr }}</pre
+ >
+ <modal-copy-button
+ :title="$options.i18n.copyToClipboardText"
+ :modal-id="modalId"
+ css-classes="border-0"
+ :target="`#${modalInfoCopyId}`"
+ />
+ </div>
+ </li>
+ </ul>
+ </li>
+ <li>{{ $options.i18n.instructions.step4 }}</li>
+ </ol>
+ <gl-link :href="$options.configuringReviewAppsPath" target="_blank">
+ {{ $options.i18n.learnMore }}
+ <gl-icon name="external-link" />
+ </gl-link>
+ <gl-link :href="$options.reviewAppsExamplesPath" target="_blank" class="gl-ml-6">
+ {{ $options.i18n.viewMoreExampleProjects }}
+ <gl-icon name="external-link" />
+ </gl-link>
</div>
- <p>
- <gl-sprintf :message="$options.instructionText.step3">
- <template #step="{ content }">
- <strong>{{ content }}</strong>
- </template>
- <template #link="{ content }">
- <gl-link :href="`blob/${defaultBranchName}/.gitlab-ci.yml`" target="_blank">{{
- content
- }}</gl-link>
- </template>
- </gl-sprintf>
- </p>
- <p>
- <gl-sprintf :message="$options.instructionText.step4">
- <template #step="{ content }">
- <strong>{{ content }}</strong>
- </template>
- <template #link="{ content }">
- <gl-link :href="$options.visualReviewsDocs" target="_blank">{{ content }}</gl-link>
- </template>
- </gl-sprintf>
- </p>
</gl-modal>
</template>
diff --git a/app/assets/javascripts/environments/components/environment_external_url.vue b/app/assets/javascripts/environments/components/environment_external_url.vue
index b8def676e7d..04a390fbba7 100644
--- a/app/assets/javascripts/environments/components/environment_external_url.vue
+++ b/app/assets/javascripts/environments/components/environment_external_url.vue
@@ -1,6 +1,8 @@
<script>
import { GlTooltipDirective, GlButton } from '@gitlab/ui';
-import { s__ } from '~/locale';
+import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
+import { s__, __ } from '~/locale';
+import { isSafeURL } from '~/lib/utils/url_utility';
/**
* Renders the external url link in environments table.
@@ -8,6 +10,7 @@ import { s__ } from '~/locale';
export default {
components: {
GlButton,
+ ModalCopyButton,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -21,11 +24,19 @@ export default {
i18n: {
title: s__('Environments|Open live environment'),
open: s__('Environments|Open'),
+ copy: __('Copy URL'),
+ copyTitle: s__('Environments|Copy live environment URL'),
+ },
+ computed: {
+ isSafeUrl() {
+ return isSafeURL(this.externalUrl);
+ },
},
};
</script>
<template>
<gl-button
+ v-if="isSafeUrl"
v-gl-tooltip
:title="$options.i18n.title"
:aria-label="$options.i18n.title"
@@ -37,4 +48,7 @@ export default {
>
{{ $options.i18n.open }}
</gl-button>
+ <modal-copy-button v-else :title="$options.i18n.copyTitle" :text="externalUrl">
+ {{ $options.i18n.copy }}
+ </modal-copy-button>
</template>
diff --git a/app/assets/javascripts/environments/components/environment_folder.vue b/app/assets/javascripts/environments/components/environment_folder.vue
index 881f404340d..2f6c54e4707 100644
--- a/app/assets/javascripts/environments/components/environment_folder.vue
+++ b/app/assets/javascripts/environments/components/environment_folder.vue
@@ -24,6 +24,10 @@ export default {
type: String,
required: true,
},
+ search: {
+ type: String,
+ required: true,
+ },
},
data() {
return { visible: false, interval: undefined };
@@ -32,7 +36,11 @@ export default {
folder: {
query: folderQuery,
variables() {
- return { environment: this.nestedEnvironment.latest, scope: this.scope };
+ return {
+ environment: this.nestedEnvironment.latest,
+ scope: this.scope,
+ search: this.search,
+ };
},
},
interval: {
diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue
index f44182e822b..55e6a891e27 100644
--- a/app/assets/javascripts/environments/components/environments_app.vue
+++ b/app/assets/javascripts/environments/components/environments_app.vue
@@ -1,7 +1,9 @@
<script>
-import { GlBadge, GlPagination, GlTab, GlTabs } from '@gitlab/ui';
+import { GlBadge, GlPagination, GlSearchBoxByType, GlTab, GlTabs } from '@gitlab/ui';
+import { debounce } from 'lodash';
import { s__, __, sprintf } from '~/locale';
import { updateHistory, setUrlParams, queryToObject } from '~/lib/utils/url_utility';
+import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import environmentAppQuery from '../graphql/queries/environment_app.query.graphql';
import pollIntervalQuery from '../graphql/queries/poll_interval.query.graphql';
import pageInfoQuery from '../graphql/queries/page_info.query.graphql';
@@ -31,6 +33,7 @@ export default {
StopEnvironmentModal,
GlBadge,
GlPagination,
+ GlSearchBoxByType,
GlTab,
GlTabs,
},
@@ -41,11 +44,10 @@ export default {
return {
scope: this.scope,
page: this.page ?? 1,
+ search: this.search,
};
},
- pollInterval() {
- return this.interval;
- },
+ pollInterval: 3000,
},
interval: {
query: pollIntervalQuery,
@@ -80,10 +82,11 @@ export default {
next: __('Next'),
prev: __('Prev'),
goto: (page) => sprintf(__('Go to page %{page}'), { page }),
+ searchPlaceholder: s__('Environments|Search by environment name'),
},
modalId: 'enable-review-app-info',
data() {
- const { page = '1', scope } = queryToObject(window.location.search);
+ const { page = '1', search = '', scope } = queryToObject(window.location.search);
return {
interval: undefined,
isReviewAppModalVisible: false,
@@ -97,6 +100,7 @@ export default {
environmentToStop: {},
environmentToChangeCanary: {},
weight: 0,
+ search,
};
},
computed: {
@@ -112,6 +116,9 @@ export default {
hasEnvironments() {
return this.environments.length > 0 || this.folders.length > 0;
},
+ hasSearch() {
+ return Boolean(this.search);
+ },
availableCount() {
return this.environmentApp?.availableCount;
},
@@ -152,11 +159,19 @@ export default {
return this.pageInfo?.perPage;
},
},
+ watch: {
+ interval(val) {
+ this.$apollo.queries.environmentApp.stopPolling();
+ this.$apollo.queries.environmentApp.startPolling(val);
+ },
+ },
mounted() {
window.addEventListener('popstate', this.syncPageFromQueryParams);
+ window.addEventListener('popstate', this.syncSearchFromQueryParams);
},
destroyed() {
window.removeEventListener('popstate', this.syncPageFromQueryParams);
+ window.removeEventListener('popstate', this.syncSearchFromQueryParams);
this.$apollo.queries.environmentApp.stopPolling();
},
methods: {
@@ -173,23 +188,24 @@ export default {
moveToPage(page) {
this.page = page;
updateHistory({
- url: setUrlParams({ page: this.page }),
+ url: setUrlParams({ page: this.page, scope: this.scope, search: this.search }),
title: document.title,
});
- this.resetPolling();
},
+ setSearch: debounce(function setSearch(input) {
+ this.search = input;
+ this.moveToPage(1);
+ }, DEFAULT_DEBOUNCE_AND_THROTTLE_MS),
syncPageFromQueryParams() {
const { page = '1' } = queryToObject(window.location.search);
this.page = parseInt(page, 10);
},
- resetPolling() {
- this.$apollo.queries.environmentApp.stopPolling();
+ syncSearchFromQueryParams() {
+ const { search = '' } = queryToObject(window.location.search);
+ this.search = search;
+ },
+ refetchEnvironments() {
this.$apollo.queries.environmentApp.refetch();
- this.$nextTick(() => {
- if (this.interval) {
- this.$apollo.queries.environmentApp.startPolling(this.interval);
- }
- });
},
},
ENVIRONMENTS_SCOPE,
@@ -237,12 +253,19 @@ export default {
</template>
</gl-tab>
</gl-tabs>
+ <gl-search-box-by-type
+ class="gl-mb-4"
+ :value="search"
+ :placeholder="$options.i18n.searchPlaceholder"
+ @input="setSearch"
+ />
<template v-if="hasEnvironments">
<environment-folder
v-for="folder in folders"
:key="folder.name"
class="gl-mb-3"
:scope="scope"
+ :search="search"
:nested-environment="folder"
/>
<environment-item
@@ -250,10 +273,15 @@ export default {
:key="environment.name"
class="gl-mb-3 gl-border-gray-100 gl-border-1 gl-border-b-solid"
:environment="environment.latest"
- @change="resetPolling"
+ @change="refetchEnvironments"
/>
</template>
- <empty-state v-else :help-path="helpPagePath" :scope="scope" />
+ <empty-state
+ v-else-if="!$apollo.queries.environmentApp.loading"
+ :help-path="helpPagePath"
+ :scope="scope"
+ :has-term="hasSearch"
+ />
<gl-pagination
align="center"
:total-items="totalItems"
diff --git a/app/assets/javascripts/environments/components/environments_detail_header.vue b/app/assets/javascripts/environments/components/environments_detail_header.vue
index bd67908a6b4..bb2f053b3fc 100644
--- a/app/assets/javascripts/environments/components/environments_detail_header.vue
+++ b/app/assets/javascripts/environments/components/environments_detail_header.vue
@@ -4,6 +4,8 @@ import csrf from '~/lib/utils/csrf';
import { __, s__ } from '~/locale';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
+import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
+import { isSafeURL } from '~/lib/utils/url_utility';
import DeleteEnvironmentModal from './delete_environment_modal.vue';
import StopEnvironmentModal from './stop_environment_modal.vue';
@@ -16,6 +18,7 @@ export default {
TimeAgo,
DeleteEnvironmentModal,
StopEnvironmentModal,
+ ModalCopyButton,
},
directives: {
GlModalDirective,
@@ -73,6 +76,8 @@ export default {
deleteButtonText: s__('Environments|Delete'),
externalButtonTitle: s__('Environments|Open live environment'),
externalButtonText: __('View deployment'),
+ copyUrlText: __('Copy URL'),
+ copyUrlTitle: s__('Environments|Copy live environment URL'),
cancelAutoStopButtonTitle: __('Prevent environment from auto-stopping'),
},
computed: {
@@ -82,6 +87,9 @@ export default {
shouldShowExternalUrlButton() {
return Boolean(this.environment.externalUrl);
},
+ isSafeUrl() {
+ return isSafeURL(this.environment.externalUrl);
+ },
shouldShowStopButton() {
return this.canStopEnvironment && this.environment.isAvailable;
},
@@ -123,16 +131,25 @@ export default {
:href="terminalPath"
icon="terminal"
/>
- <gl-button
- v-if="shouldShowExternalUrlButton"
- v-gl-tooltip.hover
- data-testid="external-url-button"
- :title="$options.i18n.externalButtonTitle"
- :href="environment.externalUrl"
- icon="external-link"
- target="_blank"
- >{{ $options.i18n.externalButtonText }}</gl-button
- >
+ <template v-if="shouldShowExternalUrlButton">
+ <gl-button
+ v-if="isSafeUrl"
+ v-gl-tooltip.hover
+ data-testid="external-url-button"
+ :title="$options.i18n.externalButtonTitle"
+ :href="environment.externalUrl"
+ icon="external-link"
+ target="_blank"
+ >{{ $options.i18n.externalButtonText }}</gl-button
+ >
+ <modal-copy-button
+ v-else
+ :title="$options.i18n.copyUrlTitle"
+ :text="environment.externalUrl"
+ >
+ {{ $options.i18n.copyUrlText }}
+ </modal-copy-button>
+ </template>
<gl-button
v-if="shouldShowExternalUrlButton"
v-gl-tooltip.hover
diff --git a/app/assets/javascripts/environments/components/new_environment.vue b/app/assets/javascripts/environments/components/new_environment.vue
index 14da2668417..bb4d6ab3428 100644
--- a/app/assets/javascripts/environments/components/new_environment.vue
+++ b/app/assets/javascripts/environments/components/new_environment.vue
@@ -1,5 +1,5 @@
<script>
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { visitUrl } from '~/lib/utils/url_utility';
import EnvironmentForm from './environment_form.vue';
@@ -32,7 +32,7 @@ export default {
.then(({ data: { path } }) => visitUrl(path))
.catch((error) => {
const message = error.response.data.message[0];
- createFlash({ message });
+ createAlert({ message });
this.loading = false;
});
},
diff --git a/app/assets/javascripts/environments/constants.js b/app/assets/javascripts/environments/constants.js
index 942491039d6..c4d02da9d21 100644
--- a/app/assets/javascripts/environments/constants.js
+++ b/app/assets/javascripts/environments/constants.js
@@ -1,4 +1,4 @@
-import { __ } from '~/locale';
+import { __, s__ } from '~/locale';
// These statuses are based on how the backend defines pod phases here
// lib/gitlab/kubernetes/pod.rb
@@ -48,3 +48,32 @@ export const ENVIRONMENT_COUNT_BY_SCOPE = {
[ENVIRONMENTS_SCOPE.AVAILABLE]: 'availableCount',
[ENVIRONMENTS_SCOPE.STOPPED]: 'stoppedCount',
};
+
+export const REVIEW_APP_MODAL_I18N = {
+ title: s__('ReviewApp|Enable Review App'),
+ intro: s__(
+ 'EnableReviewApp|Review apps are dynamic environments that you can use to provide a live preview of changes made in a feature branch.',
+ ),
+ instructions: {
+ title: s__('EnableReviewApp|To configure a dynamic review app, you must:'),
+ step1: s__(
+ 'EnableReviewApp|Have access to infrastructure that can host and deploy the review apps.',
+ ),
+ step2: s__('EnableReviewApp|Install and configure a runner to do the deployment.'),
+ step3: s__('EnableReviewApp|Add a job in your CI/CD configuration that:'),
+ step3a: s__('EnableReviewApp|Only runs for feature branches or merge requests.'),
+ step3b: s__(
+ 'EnableReviewApp|Uses a predefined CI/CD variable like %{codeStart}$(CI_COMMIT_REF_SLUG)%{codeEnd} to dynamically create the review app environments. For example, for a configuration using merge request pipelines:',
+ ),
+ step4: s__('EnableReviewApp|Recommended: Set up a job that manually stops the Review Apps.'),
+ },
+ staticSitePopover: {
+ title: s__('EnableReviewApp|Using a static site?'),
+ body: s__(
+ 'EnableReviewApp|Make sure your project has an environment configured with the target URL set to your website URL. If not, create a new one before continuing.',
+ ),
+ },
+ learnMore: __('Learn more'),
+ viewMoreExampleProjects: s__('EnableReviewApp|View more example projects'),
+ copyToClipboardText: s__('EnableReviewApp|Copy snippet'),
+};
diff --git a/app/assets/javascripts/environments/graphql/queries/environment_app.query.graphql b/app/assets/javascripts/environments/graphql/queries/environment_app.query.graphql
index c3ab9cf7fca..1a572208a1c 100644
--- a/app/assets/javascripts/environments/graphql/queries/environment_app.query.graphql
+++ b/app/assets/javascripts/environments/graphql/queries/environment_app.query.graphql
@@ -1,5 +1,5 @@
-query getEnvironmentApp($page: Int, $scope: String) {
- environmentApp(page: $page, scope: $scope) @client {
+query getEnvironmentApp($page: Int, $scope: String, $search: String) {
+ environmentApp(page: $page, scope: $scope, search: $search) @client {
availableCount
stoppedCount
environments
diff --git a/app/assets/javascripts/environments/graphql/queries/folder.query.graphql b/app/assets/javascripts/environments/graphql/queries/folder.query.graphql
index e8c145ee916..c662acb8f93 100644
--- a/app/assets/javascripts/environments/graphql/queries/folder.query.graphql
+++ b/app/assets/javascripts/environments/graphql/queries/folder.query.graphql
@@ -1,5 +1,5 @@
-query getEnvironmentFolder($environment: NestedLocalEnvironment, $scope: String) {
- folder(environment: $environment, scope: $scope) @client {
+query getEnvironmentFolder($environment: NestedLocalEnvironment, $scope: String, $search: String) {
+ folder(environment: $environment, scope: $scope, search: $search) @client {
availableCount
environments
stoppedCount
diff --git a/app/assets/javascripts/environments/graphql/resolvers.js b/app/assets/javascripts/environments/graphql/resolvers.js
index 722bb78bcf9..afd56d0cf0d 100644
--- a/app/assets/javascripts/environments/graphql/resolvers.js
+++ b/app/assets/javascripts/environments/graphql/resolvers.js
@@ -30,8 +30,8 @@ const mapEnvironment = (env) => ({
export const resolvers = (endpoint) => ({
Query: {
- environmentApp(_context, { page, scope }, { cache }) {
- return axios.get(endpoint, { params: { nested: true, page, scope } }).then((res) => {
+ environmentApp(_context, { page, scope, search }, { cache }) {
+ return axios.get(endpoint, { params: { nested: true, page, scope, search } }).then((res) => {
const headers = normalizeHeaders(res.headers);
const interval = headers['POLL-INTERVAL'];
const pageInfo = { ...parseIntPagination(headers), __typename: 'LocalPageInfo' };
@@ -59,8 +59,8 @@ export const resolvers = (endpoint) => ({
};
});
},
- folder(_, { environment: { folderPath }, scope }) {
- return axios.get(folderPath, { params: { scope, per_page: 3 } }).then((res) => ({
+ folder(_, { environment: { folderPath }, scope, search }) {
+ return axios.get(folderPath, { params: { scope, search, per_page: 3 } }).then((res) => ({
availableCount: res.data.available_count,
environments: res.data.environments.map(mapEnvironment),
stoppedCount: res.data.stopped_count,
diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js
index 8957a3074ed..5e936ad8c96 100644
--- a/app/assets/javascripts/environments/mixins/environments_mixin.js
+++ b/app/assets/javascripts/environments/mixins/environments_mixin.js
@@ -3,7 +3,7 @@
*/
import { isEqual, isFunction, omitBy } from 'lodash';
import Visibility from 'visibilityjs';
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import Poll from '~/lib/utils/poll';
import { getParameterByName } from '~/lib/utils/url_utility';
import { s__, __ } from '~/locale';
@@ -94,7 +94,7 @@ export default {
errorCallback() {
this.isLoading = false;
- createFlash({
+ createAlert({
message: s__('Environments|An error occurred while fetching the environments.'),
});
},
@@ -123,7 +123,7 @@ export default {
})
.catch((err) => {
this.isLoading = false;
- createFlash({
+ createAlert({
message: isFunction(errorMessage) ? errorMessage(err.response.data) : errorMessage,
});
});
@@ -179,7 +179,7 @@ export default {
window.location.href = url.join('/');
})
.catch(() => {
- createFlash({
+ createAlert({
message: errorMessage,
});
});