diff --git a/pkg/epinio/components/application/AppGitHubDeployment.vue b/pkg/epinio/components/application/AppGitHubDeployment.vue new file mode 100644 index 0000000000..a30ba61160 --- /dev/null +++ b/pkg/epinio/components/application/AppGitHubDeployment.vue @@ -0,0 +1,150 @@ + + + + + diff --git a/pkg/epinio/detail/applications.vue b/pkg/epinio/detail/applications.vue index ff21237b0a..0854e22eb4 100644 --- a/pkg/epinio/detail/applications.vue +++ b/pkg/epinio/detail/applications.vue @@ -1,15 +1,20 @@ diff --git a/pkg/epinio/l10n/en-us.yaml b/pkg/epinio/l10n/en-us.yaml index 655ffab15e..99f5639941 100644 --- a/pkg/epinio/l10n/en-us.yaml +++ b/pkg/epinio/l10n/en-us.yaml @@ -98,13 +98,26 @@ epinio: label: Deployment summary: Summary instances: Instances + metrics: Metrics memory: Memory cpu: CPU + details: + label: Deployment Details + origin: Origin + gitHub: + created: Created + updated: Updated + deployed: Deployed tables: label: Resources instances: Instances services: Services configs: Configurations + overview: Overview + githubCommits: Github commits + gitSource: + latestCommit: Latest commit deployed + behindCommits: Commits behind create: title: Application titleSubText: Epinio diff --git a/pkg/epinio/models/application-action.js b/pkg/epinio/models/application-action.js index 7a9d4ebd1a..cad8e3361a 100644 --- a/pkg/epinio/models/application-action.js +++ b/pkg/epinio/models/application-action.js @@ -1,7 +1,7 @@ import Resource from '@shell/plugins/dashboard-store/resource-class'; +import Vue from 'vue'; import { APPLICATION_ACTION_STATE, APPLICATION_MANIFEST_SOURCE_TYPE, APPLICATION_SOURCE_TYPE, EPINIO_PRODUCT_NAME } from '../types'; import { epinioExceptionToErrorsArray } from '../utils/errors'; -import Vue from 'vue'; export const APPLICATION_ACTION_TYPE = { CREATE_NS: 'create_namespace', @@ -166,7 +166,8 @@ export default class ApplicationActionResource extends Resource { kind: APPLICATION_MANIFEST_SOURCE_TYPE.GIT_HUB, git: { revision: source.github.commit, - repository: source.github.url + repository: source.github.url, + branch: source.github.branch }, }; } diff --git a/pkg/epinio/models/applications.js b/pkg/epinio/models/applications.js index 0ee2fb5d1c..3510e45741 100644 --- a/pkg/epinio/models/applications.js +++ b/pkg/epinio/models/applications.js @@ -1,10 +1,10 @@ -import { APPLICATION_MANIFEST_SOURCE_TYPE, EPINIO_PRODUCT_NAME, EPINIO_TYPES } from '../types'; -import { formatSi } from '@shell/utils/units'; import { classify } from '@shell/plugins/dashboard-store/classify'; -import EpinioMetaResource from './epinio-namespaced-resource'; import { downloadFile } from '@shell/utils/download'; -import { createEpinioRoute } from '../utils/custom-routing'; +import { formatSi } from '@shell/utils/units'; import { epiniofy } from '../store/epinio-store/actions'; +import { APPLICATION_ACTION_STATE, APPLICATION_MANIFEST_SOURCE_TYPE, EPINIO_PRODUCT_NAME, EPINIO_TYPES } from '../types'; +import { createEpinioRoute } from '../utils/custom-routing'; +import EpinioMetaResource from './epinio-namespaced-resource'; // See https://github.com/epinio/epinio/blob/00684bc36780a37ab90091498e5c700337015a96/pkg/api/core/v1/models/app.go#L11 const STATES = { @@ -206,6 +206,10 @@ export default class EpinioApplicationModel extends EpinioMetaResource { return Object.keys(this.configuration?.environment || []).length; } + get envDetails() { + return this.configuration?.environment; + } + get routeCount() { return this.configuration?.routes.length; } @@ -258,9 +262,10 @@ export default class EpinioApplicationModel extends EpinioMetaResource { value: this.origin.git.repository }, { label: 'Revision', - icon: 'icon-github', + icon: 'icon-commit', value: this.origin.git.revision - }] + }, + ] }; case APPLICATION_MANIFEST_SOURCE_TYPE.CONTAINER: return { @@ -272,6 +277,20 @@ export default class EpinioApplicationModel extends EpinioMetaResource { value: this.origin.Container || this.origin.container }] }; + case APPLICATION_MANIFEST_SOURCE_TYPE.GIT_HUB: + return { + label: 'GitHub', + icon: 'icon-github', + details: [ + appChart, { + label: 'Url', + value: this.origin.git.repository + }, { + label: 'Revision', + icon: 'icon-github', + value: this.origin.git.revision + }] + }; default: return undefined; } @@ -627,7 +646,7 @@ export default class EpinioApplicationModel extends EpinioMetaResource { // 'deployed' status. Unfortunately we don't have that... so wait for ready === desired replica sets instead const fresh = this.$getters['byId'](EPINIO_TYPES.APP, `${ this.meta.namespace }/${ this.meta.name }`); - if (fresh.deployment?.readyreplicas === fresh.deployment?.desiredreplicas) { + if (fresh.deployment?.readyreplicas === fresh.deployment?.desiredreplicas && fresh.deployment.state === APPLICATION_ACTION_STATE.SUCCESS) { return true; } // This is an async fn, but we're in a sync fn. It might create a backlog if previous requests don't complete in time diff --git a/pkg/epinio/models/github-commits.js b/pkg/epinio/models/github-commits.js new file mode 100644 index 0000000000..ff654c07ec --- /dev/null +++ b/pkg/epinio/models/github-commits.js @@ -0,0 +1,12 @@ +import EpinioMetaResource from '~/pkg/epinio/models/epinio-namespaced-resource'; + +export default class GithubCommits extends EpinioMetaResource { + get availableActions() { + return [{ + action: 'github-commits', + label: this.t('epinio.applications.actions.shell.label'), + icon: 'icon icon-fw icon-chevron-right', + enabled: true, + }]; + } +} diff --git a/pkg/epinio/pages/c/_cluster/applications/createapp/index.vue b/pkg/epinio/pages/c/_cluster/applications/createapp/index.vue index ff75879045..774be13a66 100644 --- a/pkg/epinio/pages/c/_cluster/applications/createapp/index.vue +++ b/pkg/epinio/pages/c/_cluster/applications/createapp/index.vue @@ -4,7 +4,7 @@ import Application from '../../../../../models/applications'; import CreateEditView from '@shell/mixins/create-edit-view/impl'; import Loading from '@shell/components/Loading.vue'; import Wizard from '@shell/components/Wizard.vue'; -import { EPINIO_TYPES } from '../../../../../types'; +import { APPLICATION_ENV_VAR, APPLICATION_SOURCE_TYPE, EPINIO_APP_ENV_VAR_GITHUB, EPINIO_TYPES } from '../../../../../types'; import { _CREATE } from '@shell/config/query-params'; import AppInfo, { EpinioAppInfo } from '../../../../../components/application/AppInfo.vue'; import AppSource, { EpinioAppSource } from '../../../../../components/application/AppSource.vue'; @@ -107,12 +107,29 @@ export default Vue.extend({ this.source = {}; const { appChart, ...cleanChanges } = changes; + this.value.configuration = this.value.configuration || {}; + if (appChart) { // app chart actually belongs in config, so stick it in there - this.value.configuration = this.value.configuration || {}; this.set(this.value.configuration, { appchart: appChart }); } + if (changes.type === APPLICATION_SOURCE_TYPE.GIT_HUB) { + this.value.configuration.environment = this.value.configuration.environment || {}; + const githubEnvVar: EPINIO_APP_ENV_VAR_GITHUB = { + usernameOrOrg: changes.github.usernameOrOrg as string, + repo: changes.github.repo, + branch: changes.github.branch, + }; + + this.set(this.value.configuration.environment, { + ...this.value.configuration.environment, + [APPLICATION_ENV_VAR]: JSON.stringify(githubEnvVar) + }); + } else { + delete this.value.configuration?.environment?.[APPLICATION_ENV_VAR]; + } + this.set(this.source, cleanChanges); }, diff --git a/pkg/epinio/types.ts b/pkg/epinio/types.ts index 02ed7aed8a..d573fb4349 100644 --- a/pkg/epinio/types.ts +++ b/pkg/epinio/types.ts @@ -50,6 +50,13 @@ export const APPLICATION_ACTION_STATE = { PENDING: 'pending', }; +export const APPLICATION_ENV_VAR = 'EPINIO_APP_DATA'; +export interface EPINIO_APP_ENV_VAR_GITHUB { + usernameOrOrg: string, + repo: string, + branch: string, +} + // -------------------------------------- // Temporary code until models are typed interface EpinioMeta { diff --git a/shell/components/__tests__/Collapse.spec.ts b/shell/components/__tests__/Collapse.spec.ts index 59d9c4290d..cb356ffcba 100644 --- a/shell/components/__tests__/Collapse.spec.ts +++ b/shell/components/__tests__/Collapse.spec.ts @@ -1,5 +1,5 @@ -import { mount } from '@vue/test-utils'; import Collapse from '@shell/components/Collapse.vue'; +import { mount } from '@vue/test-utils'; describe('component: Collapse.vue', () => { describe('closed', () => { diff --git a/shell/components/__tests__/SimpleBox.spec.ts b/shell/components/__tests__/SimpleBox.spec.ts index 06d0a9d774..37d25db24e 100644 --- a/shell/components/__tests__/SimpleBox.spec.ts +++ b/shell/components/__tests__/SimpleBox.spec.ts @@ -1,5 +1,5 @@ -import { mount } from '@vue/test-utils'; import SimpleBox from '@shell/components/SimpleBox.vue'; +import { mount } from '@vue/test-utils'; describe('component: SimpleBox.vue', () => { const wrapper = mount(SimpleBox, { propsData: { title: 'Simple box title' } }); diff --git a/shell/components/form/GithubPicker.vue b/shell/components/form/GithubPicker.vue index 8ffeb84a2b..ae10178396 100644 --- a/shell/components/form/GithubPicker.vue +++ b/shell/components/form/GithubPicker.vue @@ -53,7 +53,7 @@ export default { width: 220, label: this.t('githubPicker.tableHeaders.date.label'), value: 'date', - sort: 'date:desc', + sort: ['date:desc'], formatter: 'Date', defaultSort: true, }, diff --git a/shell/store/github.js b/shell/store/github.js index d1dba6aad4..5bae70a9e4 100644 --- a/shell/store/github.js +++ b/shell/store/github.js @@ -3,6 +3,13 @@ const GITHUB_BASE_API = 'https://api.github.com'; const fetchGithubAPI = async(endpoint) => { const response = await fetch(`${ GITHUB_BASE_API }/${ endpoint }`); + // If rate-limit is exceeded, we should wait until the rate limit is reset + if (response.status === 403) { + const resetTime = new Date(response.headers.get('X-RateLimit-Reset') * 1000); + + throw new Error(`Rate limit exceeded. Try again at ${ resetTime }`); + } + if (!response.ok) { throw response; } @@ -10,6 +17,8 @@ const fetchGithubAPI = async(endpoint) => { return await response.json(); }; +export const getters = {}; + export const actions = { async apiList(ctx, { username, endpoint, repo, branch @@ -19,6 +28,9 @@ export const actions = { case 'branches': { return await fetchGithubAPI(`repos/${ username }/${ repo }/branches?sort=updated&per_page=100&direction=desc`); } + case 'repo': { + return await fetchGithubAPI(`repos/${ username }/${ repo }`); + } case 'commits': { return await fetchGithubAPI(`repos/${ username }/${ repo }/commits?sha=${ branch }&sort=updated&per_page=100`); } @@ -51,6 +63,14 @@ export const actions = { return res; }, + async fetchRepoDetails({ commit, dispatch }, { username, repo } = {}) { + const res = await dispatch('apiList', { + username, endpoint: 'repo', repo + }); + + return res; + }, + async fetchBranches({ commit, dispatch }, { repo, username }) { const res = await dispatch('apiList', { username, endpoint: 'branches', repo @@ -59,7 +79,8 @@ export const actions = { return res; }, - async fetchCommits({ commit, dispatch }, { repo, username, branch }) { + async fetchCommits(ctx, { repo, username, branch }) { + const { dispatch } = ctx; const res = await dispatch('apiList', { username, endpoint: 'commits', repo, branch });