Persistent deployment data (#6852)

* Persists Github deployment data
* Moves GitHub Deployment card to own component
* Fixes/Tweaks
- Add indicator to app detail commits list to show deployed commit
- Add l10n
- hide github description field if there's no description (phantom icon)
- add typing for app env var
- Fix application of app env var (add/remove as appropriate)

---------

Co-authored-by: Richard Cox <richard.cox@suse.com>
This commit is contained in:
Sorin 2023-02-10 15:14:20 +01:00 committed by GitHub
parent 93381d9cf3
commit 00e389d6da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 681 additions and 131 deletions

View File

@ -0,0 +1,150 @@
<script>
import day from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
export default {
props: {
gitSource: { default: null, type: Object },
commitPosition: {
default: null,
type: Object
},
gitDeployment: {
default: null,
type: Object
},
},
methods: {
formatDate(date, from) {
day.extend(relativeTime);
return from ? day(date).fromNow() : day(date).format('DD MMM YYYY');
},
},
};
</script>
<template>
<div class="repo-info">
<div class="repo-info-owner">
<img
:src="gitSource.owner.avatar_url"
alt=""
>
<div>
<a
ref="nofollow"
target="_blank"
:href="gitSource.owner.html_url"
>{{ gitSource.owner.login }}</a>
<span>/</span>
<a
ref="nofollow"
target="_blank"
:href="gitSource.html_url"
>{{ gitSource.name }}</a>
</div>
</div>
<div
v-if="gitDeployment.deployedCommit"
class="repo-info-revision"
>
<span>
<i class="icon icon-fw icon-commit" />
{{ gitDeployment.deployedCommit.short }}
</span>
<span
v-if="commitPosition"
class="masthead-state badge-state"
>
<i class="icon icon-fw icon-commit" />
{{ commitPosition.text }}
</span>
</div>
<div
v-if="gitSource.description"
class="repo-info-description"
>
<i class="icon icon-fw icon-comment" />
<p>
{{ gitSource.description }}
</p>
</div>
<ul>
<li>
<span>{{ t('epinio.applications.detail.deployment.details.gitHub.created') }}</span>: {{ formatDate(gitSource.created_at) }}
</li>
<li>
<span>{{ t('epinio.applications.detail.deployment.details.gitHub.updated') }}</span>: {{ formatDate(gitSource.updated_at, true) }}
</li>
</ul>
</div>
</template>
<style lang="scss" scoped>
.application-card {
margin-top: 0 !important;
}
.repo-info {
display: grid;
grid-auto-columns: minmax(0, 1fr);
grid-gap: 20px;
font-size: 14px;
&-owner {
display: flex;
align-self: center;
a {
font-size: 16px !important;
}
img {
margin-right: 8px;
align-self: center;
width: 20px;
border-radius: 5%;
}
span {
opacity: 0.5;
}
}
&-description, &-revision{
display: flex;
align-items: center;
align-self: center;
i {
opacity: 0.8;
}
span {
display: flex;
align-self: center;
}
}
&-revision {
justify-content: space-between;
}
ul {
margin: 0;
padding: 0;
list-style: none;
display: flex;
justify-content: space-between;
li {
font-size: 14px;
opacity: 0.5;
span {
color: var(--default-text);
}
}
}
}
</style>

View File

@ -1,15 +1,20 @@
<script lang="ts">
import day from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import Vue, { PropType } from 'vue';
import Application from '../models/applications';
import SimpleBox from '@shell/components/SimpleBox.vue';
import ConsumptionGauge from '@shell/components/ConsumptionGauge.vue';
import { EPINIO_PRODUCT_NAME, EPINIO_TYPES } from '../types';
import { APPLICATION_ENV_VAR, EPINIO_APP_ENV_VAR_GITHUB, EPINIO_PRODUCT_NAME, EPINIO_TYPES } from '../types';
import ResourceTable from '@shell/components/ResourceTable.vue';
import PlusMinus from '@shell/components/form/PlusMinus.vue';
import { epinioExceptionToErrorsArray } from '../utils/errors';
import ApplicationCard from '@shell/components/cards/ApplicationCard.vue';
import Tabbed from '@shell/components/Tabbed/index.vue';
import Tab from '@shell/components/Tabbed/Tab.vue';
import SortableTable from '@shell/components/SortableTable/index.vue';
import AppGitHubDeployment from '../components/application/AppGitHubDeployment.vue';
import Link from '@shell/components/formatter/Link.vue';
interface Data {
}
@ -19,11 +24,14 @@ export default Vue.extend<Data, any, any, any>({
components: {
SimpleBox,
ConsumptionGauge,
SortableTable,
ResourceTable,
PlusMinus,
ApplicationCard,
AppGitHubDeployment,
Tabbed,
Tab,
Link
},
props: {
value: {
@ -42,8 +50,8 @@ export default Vue.extend<Data, any, any, any>({
fetch() {
this.$store.dispatch(`epinio/findAll`, { type: EPINIO_TYPES.SERVICE_INSTANCE });
this.$store.dispatch(`epinio/findAll`, { type: EPINIO_TYPES.CONFIGURATION });
this.fetchRepoDetails();
},
data() {
const appInstanceSchema = this.$store.getters[`${ EPINIO_PRODUCT_NAME }/schemaFor`](EPINIO_TYPES.APP_INSTANCE);
const servicesSchema = this.$store.getters[`${ EPINIO_PRODUCT_NAME }/schemaFor`](EPINIO_TYPES.SERVICE_INSTANCE);
@ -52,7 +60,12 @@ export default Vue.extend<Data, any, any, any>({
const configsHeaders: [] = this.$store.getters['type-map/headersFor'](configsSchema);
return {
saving: false,
saving: false,
gitSource: null,
gitDeployment: {
deployedCommit: null,
commitsArray: null,
},
appInstance: {
schema: appInstanceSchema,
headers: this.$store.getters['type-map/headersFor'](appInstanceSchema),
@ -64,10 +77,37 @@ export default Vue.extend<Data, any, any, any>({
configs: {
schema: configsSchema,
headers: configsHeaders.filter((h: any) => !['namespace', 'boundApps', 'service'].includes(h.name)),
}
},
commitsTableHeaders: [{
name: 'sha',
label: this.t('githubPicker.tableHeaders.sha.label'),
width: 100,
},
{
name: 'author',
label: this.t('githubPicker.tableHeaders.author.label'),
width: 190,
value: 'author.login',
sort: 'author.login',
},
{
name: 'message',
label: this.t('githubPicker.tableHeaders.message.label'),
value: 'message',
sort: 'message',
},
{
name: 'date',
width: 220,
label: this.t('githubPicker.tableHeaders.date.label'),
value: 'date',
sort: ['date:desc'],
formatter: 'Date',
defaultSort: true,
},
]
};
},
methods: {
async updateInstances(newInstances: number) {
this.$set(this, 'saving', true);
@ -84,19 +124,112 @@ export default Vue.extend<Data, any, any, any>({
const matchGithub = str.match('^(https|git)(:\/\/|@)([^\/:]+)[\/:]([^\/:]+)\/(.+)(.git)*$');
return `${ matchGithub?.[4] }/${ matchGithub?.[5] }`;
},
async fetchRepoDetails() {
const envs = this.value?.envDetails;
if (envs[APPLICATION_ENV_VAR] ) {
const { usernameOrOrg, repo } = JSON.parse(envs[APPLICATION_ENV_VAR]) as EPINIO_APP_ENV_VAR_GITHUB;
const res = await this.$store.dispatch('github/fetchRepoDetails', { username: usernameOrOrg, repo });
const {
// eslint-disable-next-line camelcase
owner, description, created_at, updated_at, html_url, name
} = res;
this.gitSource = {
owner,
description,
created_at,
updated_at,
html_url,
name
};
const commit = this.value.sourceInfo?.details.filter((ele: { label: string; }) => ele.label === 'Revision')[0]?.value;
if (commit) {
this.gitDeployment.deployedCommit = {
short: commit?.slice(0, 7),
long: commit
};
}
}
await this.fetchCommits();
},
async fetchCommits() {
const envs = this.value?.envDetails;
if (!envs[APPLICATION_ENV_VAR]) {
return;
}
const { usernameOrOrg, repo, branch } = JSON.parse(envs[APPLICATION_ENV_VAR]);
this.gitDeployment.commitsArray = await this.$store.dispatch('github/fetchCommits', {
username: usernameOrOrg, repo, branch
});
},
formatDate(date: string, from: boolean) {
day.extend(relativeTime);
return from ? day(date).fromNow() : day(date).format('DD MMM YYYY');
}
},
computed: {
prepareCommitArray() {
if (this.gitDeployment.commitsArray.length) {
return this.gitDeployment.commitsArray.reduce((acc: any, cur: any) => {
acc.push({
message: cur.commit.message,
html_url: cur.html_url,
sha: cur.sha.slice(0, 7),
commitId: cur?.sha,
author: cur.author,
isChecked: false,
date: cur?.commit.committer.date
});
return acc;
}, []);
}
return [];
},
sourceIcon(): string {
return this.value.sourceInfo?.icon || 'icon-epinio';
},
commitPosition() {
if (!this.gitDeployment?.commitsArray && !this.gitDeployment.deployedCommit) {
return;
}
let idx = null;
if (this.gitDeployment.commitsArray) {
this.gitDeployment.commitsArray.map((ele: { sha: any; }, i: number) => {
if (ele.sha === this.gitDeployment.deployedCommit.long) {
idx = i - 1;
}
});
}
if (!idx) {
return idx;
}
return {
text: ( idx - 1) >= 0 ? `${ idx } ${ this.t('epinio.applications.gitSource.behindCommits') }` : this.t('epinio.applications.gitSource.latestCommit'),
position: idx
};
}
}
});
</script>
<template>
<div>
<div class="content">
<div class="application-details">
<ApplicationCard>
<!-- Icon slot -->
@ -159,90 +292,196 @@ export default Vue.extend<Data, any, any, any>({
v-if="value.deployment"
class="deployment"
>
<div class="simple-box-row app-instances">
<SimpleBox>
<ConsumptionGauge
:resource-name="t('epinio.applications.detail.deployment.instances')"
:capacity="value.desiredInstances"
:used="value.readyInstances"
:used-as-resource-name="true"
:color-stops="{ 70: '--success', 30: '--warning', 0: '--error' }"
/>
<div class="scale-instances">
<PlusMinus
class="mt-15 mb-10"
:value="value.desiredInstances"
:disabled="saving"
@minus="updateInstances(value.desiredInstances - 1)"
@plus="updateInstances(value.desiredInstances + 1)"
/>
</div>
</SimpleBox>
<!-- Source information -->
<SimpleBox v-if="value.sourceInfo">
<div class="deployment__origin__row">
<h4>Deployment Details</h4>
</div>
<div class="deployment__origin__list">
<ul>
<li>
<h4>Origin</h4>
<span v-if="value.sourceInfo.label === 'Git'">
<i class="icon icon-fw icon-github" />
{{ value.sourceInfo.label }}
</span>
<span v-else>{{ value.sourceInfo.label }}</span>
</li>
<!-- Source information -->
<Tabbed>
<Tab
label-key="epinio.applications.detail.tables.overview"
name="overview"
:weight="3"
>
<div class="simple-box-row app-instances">
<SimpleBox>
<ConsumptionGauge
:resource-name="t('epinio.applications.detail.deployment.instances')"
:capacity="value.desiredInstances"
:used="value.readyInstances"
:used-as-resource-name="true"
:color-stops="{ 70: '--success', 30: '--warning', 0: '--error' }"
/>
<div class="scale-instances">
<PlusMinus
class="mt-15 mb-10"
:value="value.desiredInstances"
:disabled="saving"
@minus="updateInstances(value.desiredInstances - 1)"
@plus="updateInstances(value.desiredInstances + 1)"
/>
</div>
<li
v-for="d of value.sourceInfo.details"
:key="d.label"
<div class="deployment__origin__row">
<hr class="mt-10 mb-10">
<h4 class="mt-10 mb-10">
{{ t('epinio.applications.detail.deployment.metrics') }}
</h4>
<div
v-if="gitSource"
class="stats"
>
<div>
<h3>{{ t('tableHeaders.memory') }}</h3>
<ul>
<li> <span>Min: </span> {{ value.instanceMemory.min }}</li>
<li> <span>Max: </span>{{ value.instanceMemory.max }}</li>
<li><span>Avg: </span>{{ value.instanceMemory.avg }}</li>
</ul>
</div>
<div>
<h3>{{ t('tableHeaders.cpu') }}</h3>
<ul>
<li> <span>Min: </span> {{ value.instanceCpu.min }}</li>
<li> <span>Max: </span>{{ value.instanceCpu.max }}</li>
<li><span>Avg: </span>{{ value.instanceCpu.avg }}</li>
</ul>
</div>
</div>
<div
v-else
class="stats-table"
>
<table class="mt-15">
<thead>
<tr>
<th />
<th>Min</th>
<th>Max</th>
<th>Avg</th>
</tr>
</thead>
<tr>
<td>{{ t('tableHeaders.memory') }}</td>
<td>{{ value.instanceMemory.min }}</td>
<td>{{ value.instanceMemory.max }}</td>
<td>{{ value.instanceMemory.avg }}</td>
</tr>
<tr>
<td>{{ t('tableHeaders.cpu') }}</td>
<td>{{ value.instanceCpu.min }}</td>
<td>{{ value.instanceCpu.max }}</td>
<td>{{ value.instanceCpu.avg }}</td>
</tr>
</table>
</div>
</div>
</SimpleBox>
<SimpleBox v-if="value.sourceInfo">
<h4 class="mb-10">
{{ t('epinio.applications.detail.deployment.details.label') }}
</h4>
<div
v-if="gitSource"
class="repo-info"
>
<h4>{{ d.label }}</h4>
<span v-if="d.value && d.value.startsWith('http')">
<a
:href="d.value"
target="_blank"
>{{ formatURL(d.value) }}</a>
</span>
<span v-else>{{ d.value }}</span>
</li>
<AppGitHubDeployment
:git-deployment="gitDeployment"
:git-source="gitSource"
:commit-position="commitPosition"
/>
</div>
<hr class="mt-10 mb-10">
<div class="deployment__origin__list">
<ul>
<li>
<h4>{{ t('epinio.applications.detail.deployment.details.origin') }}</h4>
<span>{{ value.sourceInfo.label }}</span>
</li>
<li>
<h4>{{ t('epinio.applications.tableHeaders.deployedBy') }}</h4>
<span> {{ value.deployment.username }}</span>
</li>
</ul>
<li
v-for="d of value.sourceInfo.details"
:key="d.label"
>
<h4>{{ d.label }}</h4>
<span v-if="d.value && d.value.startsWith('http')">
<a
:href="d.value"
target="_blank"
>{{ formatURL(d.value) }}</a>
</span>
<span v-else-if="gitSource && d.value && d.value.match(/^[a-f0-9]{40}$/)">
<a
:href="`${gitSource.html_url}/commit/${d.value}`"
target="_blank"
>{{ d.value }}</a>
</span>
<span v-else>{{ d.value }}</span>
</li>
<li>
<h4>{{ t('epinio.applications.tableHeaders.deployedBy') }}</h4>
<span> {{ value.deployment.username }}</span>
</li>
</ul>
</div>
</SimpleBox>
</div>
</SimpleBox>
<SimpleBox>
<div class="deployment__origin__row">
<h4>Application Metrics</h4>
<table class="stats mt-15">
<thead>
<tr>
<th />
<th>Min</th>
<th>Max</th>
<th>Avg</th>
</tr>
</thead>
<tr>
<td>{{ t('tableHeaders.memory') }}</td>
<td>{{ value.instanceMemory.min }}</td>
<td>{{ value.instanceMemory.max }}</td>
<td>{{ value.instanceMemory.avg }}</td>
</tr>
<tr>
<td>{{ t('tableHeaders.cpu') }}</td>
<td>{{ value.instanceCpu.min }}</td>
<td>{{ value.instanceCpu.max }}</td>
<td>{{ value.instanceCpu.avg }}</td>
</tr>
</table>
</div>
</SimpleBox>
</div>
</Tab>
<Tab
v-if="gitSource"
label-key="epinio.applications.detail.tables.githubCommits"
name="githubCommits"
:weight="2"
>
<SortableTable
v-if="gitDeployment.commitsArray"
:rows="prepareCommitArray"
:headers="commitsTableHeaders"
mode="view"
key-field="sha"
:search="true"
:paging="true"
:table-actions="false"
:row-actions="false"
:rows-per-page="10"
>
<template #cell:author="{row}">
<div class="sortable-table-avatar">
<template v-if="row.author">
<img
:src="row.author.avatar_url"
alt=""
>
<a
:href="row.author.html_url"
target="_blank"
rel="nofollow noopener noreferrer"
>
{{ row.author.login }}
</a>
</template>
<template v-else>
{{ t('githubPicker.tableHeaders.author.unknown') }}
</template>
</div>
</template>
<template #cell:sha="{row}">
<div class="sortable-table-commit">
<Link
:row="row"
url-key="html_url"
:value="row.sha"
/>
<i
v-if="row.sha === gitDeployment.deployedCommit.short"
v-tooltip="t('epinio.applications.detail.deployment.details.gitHub.deployed')"
class="icon icon-fw icon-commit"
/>
</div>
</template>
</SortableTable>
</Tab>
</Tabbed>
</div>
<h3 class="mt-20">
@ -295,6 +534,9 @@ export default Vue.extend<Data, any, any, any>({
</template>
<style lang="scss" scoped>
.content {
max-width: 1600px;
}
.simple-box-row {
display: grid;
grid-auto-columns: minmax(0, 1fr);
@ -308,7 +550,6 @@ export default Vue.extend<Data, any, any, any>({
width: 100%;
ul {
word-break: break-all;
padding-left: 20px;
}
&:not(:last-of-type) {
margin-right: 20px;
@ -332,28 +573,22 @@ export default Vue.extend<Data, any, any, any>({
tr {
th {
text-align: left;
color: #c4c4c4;
color: var(--muted);
font-weight: 300;
}
}
}
}
.deployment__origin__list {
ul {
margin: 20px 0;
padding: 0;
display: grid;
grid-template-columns: 1fr 1fr;
li {
margin: 5px;
list-style: none;
h4 {
color: #c4c4c4;
font-weight: 300;
margin: 0;
}
}
.scale-instances {
display: flex;
align-items: center;
.plus-minus {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
}
}
@ -394,22 +629,97 @@ export default Vue.extend<Data, any, any, any>({
}
}
.deployment {
.simple-box {
.stats-table {
display: flex;
width: 100%;
table {
width: 100%;
margin-bottom: 0;
}
.app-instances {
tr td {
min-width: 58px;
padding: 5px 0;
font-size: 1.1rem;
}
.stats {
display: grid;
grid-template-columns: 1fr 1fr;
margin: 12px 0;
position: relative;
&::before {
content: "";
border-right: 1px solid var(--default);
position: absolute;
top: 0;
bottom: 0;
left: 50%;
width: 1px;
}
& > div:nth-child(2) {
display: flex;
flex-direction: column;
align-items: flex-end;
}
h3 {
font-size: 16px;
}
ul {
display: flex;
flex-direction: column;
gap: 8px;
margin: 0;
padding: 0;
li {
list-style: none;
font-size: 14px;
}
.scale-instances {
margin-top: 20px;
display: flex;
justify-content: center;
}
// For the second div in stats, style the ul differently
& > div:nth-child(2) ul {
align-items: flex-end;
}
}
.deployment__origin__list {
ul {
margin: 0;
padding: 0;
display: grid;
grid-template-columns: 1fr 1fr;
li {
margin: 5px;
list-style: none;
h4 {
color: var(--default-text);
font-weight: 300;
font-size: 14px;
margin: 0;
}
}
}
}
.sortable-table {
&-avatar {
display: flex;
align-items: center;
justify-content: flex-start;
img {
width: 30px;
height: 30px;
border-radius: var(--border-radius);
margin-right: 10px;
}
}
&-commit {
display: flex;
}
}
</style>

View File

@ -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

View File

@ -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
},
};
}

View File

@ -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

View File

@ -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,
}];
}
}

View File

@ -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<Data, any, any, any>({
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);
},

View File

@ -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 {

View File

@ -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', () => {

View File

@ -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' } });

View File

@ -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,
},

View File

@ -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
});