mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-01-09 17:15:31 +00:00
Hide not owned repos from sidebar and repo list (#1453)
Co-authored-by: Lukas <lukas@slucky.de>
This commit is contained in:
parent
420ac54e62
commit
4c97a0104e
13 changed files with 188 additions and 219 deletions
|
@ -49,6 +49,26 @@ module.exports = {
|
||||||
'@typescript-eslint/no-explicit-any': 'error',
|
'@typescript-eslint/no-explicit-any': 'error',
|
||||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
'@typescript-eslint/no-non-null-assertion': 'error',
|
'@typescript-eslint/no-non-null-assertion': 'error',
|
||||||
|
// SOURCE: https://github.com/iamturns/eslint-config-airbnb-typescript/blob/4aec5702be5b4e74e0e2f40bc78b4bc961681de1/lib/shared.js#L41
|
||||||
|
'@typescript-eslint/naming-convention': [
|
||||||
|
'error',
|
||||||
|
// Allow camelCase variables (23.2), PascalCase variables (23.8), and UPPER_CASE variables (23.10)
|
||||||
|
{
|
||||||
|
selector: 'variable',
|
||||||
|
format: ['camelCase', 'PascalCase', 'UPPER_CASE'],
|
||||||
|
leadingUnderscore: 'allow',
|
||||||
|
},
|
||||||
|
// Allow camelCase functions (23.2), and PascalCase functions (23.8)
|
||||||
|
{
|
||||||
|
selector: 'function',
|
||||||
|
format: ['camelCase', 'PascalCase'],
|
||||||
|
},
|
||||||
|
// Airbnb recommends PascalCase for classes (23.3), and although Airbnb does not make TypeScript recommendations, we are assuming this rule would similarly apply to anything "type like", including interfaces, type aliases, and enums
|
||||||
|
{
|
||||||
|
selector: 'typeLike',
|
||||||
|
format: ['PascalCase'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
'import/no-unresolved': 'off', // disable as this is handled by tsc itself
|
'import/no-unresolved': 'off', // disable as this is handled by tsc itself
|
||||||
'import/first': 'error',
|
'import/first': 'error',
|
||||||
|
|
42
web/components.d.ts
vendored
42
web/components.d.ts
vendored
|
@ -22,51 +22,9 @@ declare module '@vue/runtime-core' {
|
||||||
FluidContainer: typeof import('./src/components/layout/FluidContainer.vue')['default']
|
FluidContainer: typeof import('./src/components/layout/FluidContainer.vue')['default']
|
||||||
GeneralTab: typeof import('./src/components/repo/settings/GeneralTab.vue')['default']
|
GeneralTab: typeof import('./src/components/repo/settings/GeneralTab.vue')['default']
|
||||||
Header: typeof import('./src/components/layout/scaffold/Header.vue')['default']
|
Header: typeof import('./src/components/layout/scaffold/Header.vue')['default']
|
||||||
IBiCheckCircleFill: typeof import('~icons/bi/check-circle-fill')['default']
|
|
||||||
IBiCircle: typeof import('~icons/bi/circle')['default']
|
|
||||||
IBiPlayCircleFill: typeof import('~icons/bi/play-circle-fill')['default']
|
|
||||||
IBiSlashCircleFill: typeof import('~icons/bi/slash-circle-fill')['default']
|
|
||||||
IBiStopCircleFill: typeof import('~icons/bi/stop-circle-fill')['default']
|
|
||||||
IBiXCircleFill: typeof import('~icons/bi/x-circle-fill')['default']
|
|
||||||
IBxBxPowerOff: typeof import('~icons/bx/bx-power-off')['default']
|
|
||||||
ICarbonCloseOutline: typeof import('~icons/carbon/close-outline')['default']
|
|
||||||
ICarbonInProgress: typeof import('~icons/carbon/in-progress')['default']
|
|
||||||
IClarityDeployLine: typeof import('~icons/clarity/deploy-line')['default']
|
|
||||||
IClaritySettingsSolid: typeof import('~icons/clarity/settings-solid')['default']
|
|
||||||
Icon: typeof import('./src/components/atomic/Icon.vue')['default']
|
Icon: typeof import('./src/components/atomic/Icon.vue')['default']
|
||||||
IconButton: typeof import('./src/components/atomic/IconButton.vue')['default']
|
IconButton: typeof import('./src/components/atomic/IconButton.vue')['default']
|
||||||
IGgTrash: typeof import('~icons/gg/trash')['default']
|
|
||||||
IIcBaselineDarkMode: typeof import('~icons/ic/baseline-dark-mode')['default']
|
|
||||||
IIcBaselineDownloadForOffline: typeof import('~icons/ic/baseline-download-for-offline')['default']
|
|
||||||
IIcBaselineEdit: typeof import('~icons/ic/baseline-edit')['default']
|
|
||||||
IIcBaselineFileDownload: typeof import('~icons/ic/baseline-file-download')['default']
|
|
||||||
IIcBaselineFileDownloadOff: typeof import('~icons/ic/baseline-file-download-off')['default']
|
|
||||||
IIcBaselineHealing: typeof import('~icons/ic/baseline-healing')['default']
|
|
||||||
IIcBaselinePlayArrow: typeof import('~icons/ic/baseline-play-arrow')['default']
|
|
||||||
IIconoirArrowLeft: typeof import('~icons/iconoir/arrow-left')['default']
|
|
||||||
IIconParkOutlineAlarmClock: typeof import('~icons/icon-park-outline/alarm-clock')['default']
|
|
||||||
IIcRoundLightMode: typeof import('~icons/ic/round-light-mode')['default']
|
|
||||||
IIcSharpTimelapse: typeof import('~icons/ic/sharp-timelapse')['default']
|
|
||||||
IIcTwotoneAdd: typeof import('~icons/ic/twotone-add')['default']
|
|
||||||
ILaTimes: typeof import('~icons/la/times')['default']
|
|
||||||
IMdiBitbucket: typeof import('~icons/mdi/bitbucket')['default']
|
|
||||||
IMdiChevronRight: typeof import('~icons/mdi/chevron-right')['default']
|
|
||||||
IMdiClockTimeEightOutline: typeof import('~icons/mdi/clock-time-eight-outline')['default']
|
|
||||||
IMdiFormatListBulleted: typeof import('~icons/mdi/format-list-bulleted')['default']
|
|
||||||
IMdiGestureTap: typeof import('~icons/mdi/gesture-tap')['default']
|
|
||||||
IMdiGithub: typeof import('~icons/mdi/github')['default']
|
|
||||||
IMdiLoading: typeof import('~icons/mdi/loading')['default']
|
|
||||||
IMdiSync: typeof import('~icons/mdi/sync')['default']
|
|
||||||
IMdiSourceBranch: typeof import('~icons/mdi/source-branch')['default']
|
|
||||||
IMdisourceCommit: typeof import('~icons/mdi/source-commit')['default']
|
|
||||||
IMdiSourcePull: typeof import('~icons/mdi/source-pull')['default']
|
|
||||||
IMdiTagOutline: typeof import('~icons/mdi/tag-outline')['default']
|
|
||||||
InputField: typeof import('./src/components/form/InputField.vue')['default']
|
InputField: typeof import('./src/components/form/InputField.vue')['default']
|
||||||
IPhGitlabLogoSimpleFill: typeof import('~icons/ph/gitlab-logo-simple-fill')['default']
|
|
||||||
ISimpleIconsGitea: typeof import('~icons/simple-icons/gitea')['default']
|
|
||||||
ITeenyiconsGitSolid: typeof import('~icons/teenyicons/git-solid')['default']
|
|
||||||
ITeenyiconsRefreshOutline: typeof import('~icons/teenyicons/refresh-outline')['default']
|
|
||||||
IVaadinQuestionCircleO: typeof import('~icons/vaadin/question-circle-o')['default']
|
|
||||||
ListItem: typeof import('./src/components/atomic/ListItem.vue')['default']
|
ListItem: typeof import('./src/components/atomic/ListItem.vue')['default']
|
||||||
ManualPipelinePopup: typeof import('./src/components/layout/popups/ManualPipelinePopup.vue')['default']
|
ManualPipelinePopup: typeof import('./src/components/layout/popups/ManualPipelinePopup.vue')['default']
|
||||||
Navbar: typeof import('./src/components/layout/header/Navbar.vue')['default']
|
Navbar: typeof import('./src/components/layout/header/Navbar.vue')['default']
|
||||||
|
|
|
@ -14,26 +14,18 @@
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, onMounted } from 'vue';
|
import { onMounted, toRef } from 'vue';
|
||||||
|
|
||||||
import IconButton from '~/components/atomic/IconButton.vue';
|
import IconButton from '~/components/atomic/IconButton.vue';
|
||||||
import usePipelineFeed from '~/compositions/usePipelineFeed';
|
import usePipelineFeed from '~/compositions/usePipelineFeed';
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'ActivePipelines',
|
|
||||||
|
|
||||||
components: { IconButton },
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const pipelineFeed = usePipelineFeed();
|
const pipelineFeed = usePipelineFeed();
|
||||||
|
const activePipelines = toRef(pipelineFeed, 'activePipelines');
|
||||||
|
const { toggle } = pipelineFeed;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
pipelineFeed.load();
|
await pipelineFeed.load();
|
||||||
});
|
|
||||||
|
|
||||||
return pipelineFeed;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -100,7 +100,7 @@ import { useAsyncAction } from '~/compositions/useAsyncAction';
|
||||||
import useAuthentication from '~/compositions/useAuthentication';
|
import useAuthentication from '~/compositions/useAuthentication';
|
||||||
import useNotifications from '~/compositions/useNotifications';
|
import useNotifications from '~/compositions/useNotifications';
|
||||||
import { Repo, RepoSettings, RepoVisibility, WebhookEvents } from '~/lib/api/types';
|
import { Repo, RepoSettings, RepoVisibility, WebhookEvents } from '~/lib/api/types';
|
||||||
import RepoStore from '~/store/repos';
|
import { useRepoStore } from '~/store/repos';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'GeneralTab',
|
name: 'GeneralTab',
|
||||||
|
@ -111,7 +111,7 @@ export default defineComponent({
|
||||||
const apiClient = useApiClient();
|
const apiClient = useApiClient();
|
||||||
const notifications = useNotifications();
|
const notifications = useNotifications();
|
||||||
const { user } = useAuthentication();
|
const { user } = useAuthentication();
|
||||||
const repoStore = RepoStore();
|
const repoStore = useRepoStore();
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
|
||||||
const repo = inject<Ref<Repo>>('repo');
|
const repo = inject<Ref<Repo>>('repo');
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import PipelineStore from '~/store/pipelines';
|
import { usePipelineStore } from '~/store/pipelines';
|
||||||
import RepoStore from '~/store/repos';
|
import { useRepoStore } from '~/store/repos';
|
||||||
import { repoSlug } from '~/utils/helpers';
|
|
||||||
|
|
||||||
import useApiClient from './useApiClient';
|
import useApiClient from './useApiClient';
|
||||||
|
|
||||||
|
@ -11,8 +10,8 @@ export default () => {
|
||||||
if (initialized) {
|
if (initialized) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const repoStore = RepoStore();
|
const repoStore = useRepoStore();
|
||||||
const pipelineStore = PipelineStore();
|
const pipelineStore = usePipelineStore();
|
||||||
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
|
|
||||||
|
@ -30,7 +29,6 @@ export default () => {
|
||||||
}
|
}
|
||||||
const { pipeline } = data;
|
const { pipeline } = data;
|
||||||
pipelineStore.setPipeline(repo.owner, repo.name, pipeline);
|
pipelineStore.setPipeline(repo.owner, repo.name, pipeline);
|
||||||
pipelineStore.setPipelineFeedItem({ ...pipeline, name: repo.name, owner: repo.owner, full_name: repoSlug(repo) });
|
|
||||||
|
|
||||||
// contains step update
|
// contains step update
|
||||||
if (!data.step) {
|
if (!data.step) {
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { computed, toRef } from 'vue';
|
import { computed, toRef } from 'vue';
|
||||||
|
|
||||||
import useUserConfig from '~/compositions/useUserConfig';
|
import useUserConfig from '~/compositions/useUserConfig';
|
||||||
import PipelineStore from '~/store/pipelines';
|
import { usePipelineStore } from '~/store/pipelines';
|
||||||
|
|
||||||
import useAuthentication from './useAuthentication';
|
import useAuthentication from './useAuthentication';
|
||||||
|
|
||||||
const { userConfig, setUserConfig } = useUserConfig();
|
const { userConfig, setUserConfig } = useUserConfig();
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const pipelineStore = PipelineStore();
|
const pipelineStore = usePipelineStore();
|
||||||
const { isAuthenticated } = useAuthentication();
|
const { isAuthenticated } = useAuthentication();
|
||||||
|
|
||||||
const isOpen = computed(() => userConfig.value.isPipelineFeedOpen && !!isAuthenticated);
|
const isOpen = computed(() => userConfig.value.isPipelineFeedOpen && !!isAuthenticated);
|
||||||
|
@ -17,7 +17,7 @@ export default () => {
|
||||||
setUserConfig('isPipelineFeedOpen', !userConfig.value.isPipelineFeedOpen);
|
setUserConfig('isPipelineFeedOpen', !userConfig.value.isPipelineFeedOpen);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortedPipelines = toRef(pipelineStore, 'sortedPipelineFeed');
|
const sortedPipelines = toRef(pipelineStore, 'pipelineFeed');
|
||||||
const activePipelines = toRef(pipelineStore, 'activePipelines');
|
const activePipelines = toRef(pipelineStore, 'activePipelines');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -108,7 +108,6 @@ export default class ApiClient {
|
||||||
const query = encodeQueryString({
|
const query = encodeQueryString({
|
||||||
access_token: this.token || undefined,
|
access_token: this.token || undefined,
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
let _path = this.server ? this.server + path : path;
|
let _path = this.server ? this.server + path : path;
|
||||||
_path = this.token ? `${path}?${query}` : path;
|
_path = this.token ? `${path}?${query}` : path;
|
||||||
|
|
||||||
|
|
|
@ -1,50 +1,41 @@
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { computed, Ref, ref, toRef } from 'vue';
|
import { computed, reactive, Ref, ref } from 'vue';
|
||||||
|
|
||||||
import useApiClient from '~/compositions/useApiClient';
|
import useApiClient from '~/compositions/useApiClient';
|
||||||
import { Pipeline, PipelineFeed, PipelineStep } from '~/lib/api/types';
|
import { Pipeline, PipelineFeed, PipelineStep } from '~/lib/api/types';
|
||||||
|
import { useRepoStore } from '~/store/repos';
|
||||||
import { comparePipelines, isPipelineActive, repoSlug } from '~/utils/helpers';
|
import { comparePipelines, isPipelineActive, repoSlug } from '~/utils/helpers';
|
||||||
|
|
||||||
|
export const usePipelineStore = defineStore('pipelines', () => {
|
||||||
const apiClient = useApiClient();
|
const apiClient = useApiClient();
|
||||||
|
const repoStore = useRepoStore();
|
||||||
|
|
||||||
export default defineStore({
|
const pipelines: Map<string, Map<number, Pipeline>> = reactive(new Map());
|
||||||
id: 'pipelines',
|
|
||||||
|
|
||||||
state: () => ({
|
function setPipeline(owner: string, repo: string, pipeline: Pipeline) {
|
||||||
pipelines: {} as Record<string, Record<number, Pipeline>>,
|
|
||||||
pipelineFeed: [] as PipelineFeed[],
|
|
||||||
}),
|
|
||||||
|
|
||||||
getters: {
|
|
||||||
sortedPipelineFeed(state) {
|
|
||||||
return state.pipelineFeed.sort(comparePipelines);
|
|
||||||
},
|
|
||||||
activePipelines(state) {
|
|
||||||
return state.pipelineFeed.filter(isPipelineActive);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
// setters
|
|
||||||
setPipeline(owner: string, repo: string, pipeline: Pipeline) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
const _repoSlug = repoSlug(owner, repo);
|
const _repoSlug = repoSlug(owner, repo);
|
||||||
if (!this.pipelines[_repoSlug]) {
|
const repoPipelines = pipelines.get(_repoSlug) || new Map();
|
||||||
this.pipelines[_repoSlug] = {};
|
repoPipelines.set(pipeline.number, pipeline);
|
||||||
|
pipelines.set(_repoSlug, repoPipelines);
|
||||||
}
|
}
|
||||||
|
|
||||||
const repoPipelines = this.pipelines[_repoSlug];
|
function getRepoPipelines(owner: Ref<string>, repo: Ref<string>) {
|
||||||
|
return computed(() => {
|
||||||
|
const slug = repoSlug(owner.value, repo.value);
|
||||||
|
return Array.from(pipelines.get(slug)?.values() || []).sort(comparePipelines);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// merge with available data
|
function getPipeline(owner: Ref<string>, repo: Ref<string>, _pipelineNumber: Ref<string>) {
|
||||||
repoPipelines[pipeline.number] = { ...(repoPipelines[pipeline.number] || {}), ...pipeline };
|
return computed(() => {
|
||||||
|
const slug = repoSlug(owner.value, repo.value);
|
||||||
|
const pipelineNumber = parseInt(_pipelineNumber.value, 10);
|
||||||
|
return pipelines.get(slug)?.get(pipelineNumber);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.pipelines = {
|
function setStep(owner: string, repo: string, pipelineNumber: number, step: PipelineStep) {
|
||||||
...this.pipelines,
|
const pipeline = getPipeline(ref(owner), ref(repo), ref(pipelineNumber.toString())).value;
|
||||||
[_repoSlug]: repoPipelines,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
setStep(owner: string, repo: string, pipelineNumber: number, step: PipelineStep) {
|
|
||||||
const pipeline = this.getPipeline(ref(owner), ref(repo), ref(pipelineNumber.toString())).value;
|
|
||||||
if (!pipeline) {
|
if (!pipeline) {
|
||||||
throw new Error("Can't find pipeline");
|
throw new Error("Can't find pipeline");
|
||||||
}
|
}
|
||||||
|
@ -54,46 +45,61 @@ export default defineStore({
|
||||||
}
|
}
|
||||||
|
|
||||||
pipeline.steps = [...pipeline.steps.filter((p) => p.pid !== step.pid), step];
|
pipeline.steps = [...pipeline.steps.filter((p) => p.pid !== step.pid), step];
|
||||||
this.setPipeline(owner, repo, pipeline);
|
setPipeline(owner, repo, pipeline);
|
||||||
},
|
}
|
||||||
setPipelineFeedItem(pipeline: PipelineFeed) {
|
|
||||||
const pipelineFeed = this.pipelineFeed.filter((b) => b.id !== pipeline.id);
|
|
||||||
this.pipelineFeed = [...pipelineFeed, pipeline];
|
|
||||||
},
|
|
||||||
|
|
||||||
// getters
|
async function loadRepoPipelines(owner: string, repo: string) {
|
||||||
getPipelines(owner: Ref<string>, repo: Ref<string>) {
|
const _pipelines = await apiClient.getPipelineList(owner, repo);
|
||||||
return computed(() => {
|
_pipelines.forEach((pipeline) => {
|
||||||
const slug = repoSlug(owner.value, repo.value);
|
setPipeline(owner, repo, pipeline);
|
||||||
return toRef(this.pipelines, slug).value;
|
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
getSortedPipelines(owner: Ref<string>, repo: Ref<string>) {
|
|
||||||
return computed(() => Object.values(this.getPipelines(owner, repo).value || []).sort(comparePipelines));
|
|
||||||
},
|
|
||||||
getActivePipelines(owner: Ref<string>, repo: Ref<string>) {
|
|
||||||
const pipelines = this.getPipelines(owner, repo);
|
|
||||||
return computed(() => Object.values(pipelines.value).filter(isPipelineActive));
|
|
||||||
},
|
|
||||||
getPipeline(owner: Ref<string>, repo: Ref<string>, pipelineNumber: Ref<string>) {
|
|
||||||
const pipelines = this.getPipelines(owner, repo);
|
|
||||||
return computed(() => (pipelines.value || {})[parseInt(pipelineNumber.value, 10)]);
|
|
||||||
},
|
|
||||||
|
|
||||||
// loading
|
async function loadPipeline(owner: string, repo: string, pipelinesNumber: number) {
|
||||||
async loadPipelines(owner: string, repo: string) {
|
const pipeline = await apiClient.getPipeline(owner, repo, pipelinesNumber);
|
||||||
const pipelines = await apiClient.getPipelineList(owner, repo);
|
setPipeline(owner, repo, pipeline);
|
||||||
pipelines.forEach((pipeline) => {
|
}
|
||||||
this.setPipeline(owner, repo, pipeline);
|
|
||||||
|
const pipelineFeed = computed(() =>
|
||||||
|
Array.from(pipelines.entries())
|
||||||
|
.reduce<PipelineFeed[]>((acc, [_repoSlug, repoPipelines]) => {
|
||||||
|
const repoPipelinesArray = Array.from(repoPipelines.entries()).map(
|
||||||
|
([_pipelineNumber, pipeline]) =>
|
||||||
|
<PipelineFeed>{
|
||||||
|
...pipeline,
|
||||||
|
full_name: _repoSlug,
|
||||||
|
owner: _repoSlug.split('/')[0],
|
||||||
|
name: _repoSlug.split('/')[1],
|
||||||
|
number: _pipelineNumber,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return [...acc, ...repoPipelinesArray];
|
||||||
|
}, [])
|
||||||
|
.sort(comparePipelines)
|
||||||
|
.filter((pipeline) => repoStore.ownedRepoSlugs.includes(pipeline.full_name)),
|
||||||
|
);
|
||||||
|
|
||||||
|
const activePipelines = computed(() => pipelineFeed.value.filter(isPipelineActive));
|
||||||
|
|
||||||
|
async function loadPipelineFeed() {
|
||||||
|
await repoStore.loadRepos();
|
||||||
|
|
||||||
|
const _pipelines = await apiClient.getPipelineFeed();
|
||||||
|
_pipelines.forEach((pipeline) => {
|
||||||
|
setPipeline(pipeline.owner, pipeline.name, pipeline);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
async loadPipeline(owner: string, repo: string, pipelinesNumber: number) {
|
|
||||||
const pipelines = await apiClient.getPipeline(owner, repo, pipelinesNumber);
|
return {
|
||||||
this.setPipeline(owner, repo, pipelines);
|
pipelines,
|
||||||
},
|
setPipeline,
|
||||||
async loadPipelineFeed() {
|
setStep,
|
||||||
const pipelines = await apiClient.getPipelineFeed();
|
getRepoPipelines,
|
||||||
this.pipelineFeed = pipelines;
|
getPipeline,
|
||||||
},
|
loadRepoPipelines,
|
||||||
},
|
loadPipeline,
|
||||||
|
activePipelines,
|
||||||
|
pipelineFeed,
|
||||||
|
loadPipelineFeed,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,44 +1,54 @@
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { computed, Ref, toRef } from 'vue';
|
import { computed, reactive, Ref, ref } from 'vue';
|
||||||
|
|
||||||
import useApiClient from '~/compositions/useApiClient';
|
import useApiClient from '~/compositions/useApiClient';
|
||||||
import { Repo } from '~/lib/api/types';
|
import { Repo } from '~/lib/api/types';
|
||||||
import { repoSlug } from '~/utils/helpers';
|
import { repoSlug } from '~/utils/helpers';
|
||||||
|
|
||||||
|
export const useRepoStore = defineStore('repos', () => {
|
||||||
const apiClient = useApiClient();
|
const apiClient = useApiClient();
|
||||||
|
|
||||||
export default defineStore({
|
const repos: Map<string, Repo> = reactive(new Map());
|
||||||
id: 'repos',
|
const ownedRepoSlugs = ref<string[]>([]);
|
||||||
|
|
||||||
state: () => ({
|
const ownedRepos = computed(() =>
|
||||||
repos: {} as Record<string, Repo>,
|
Array.from(repos.entries())
|
||||||
}),
|
.filter(([slug]) => ownedRepoSlugs.value.includes(slug))
|
||||||
|
.map(([, repo]) => repo),
|
||||||
|
);
|
||||||
|
|
||||||
actions: {
|
function getRepo(owner: Ref<string>, name: Ref<string>) {
|
||||||
// getter
|
|
||||||
getRepo(owner: Ref<string>, name: Ref<string>) {
|
|
||||||
return computed(() => {
|
return computed(() => {
|
||||||
const slug = repoSlug(owner.value, name.value);
|
const slug = repoSlug(owner.value, name.value);
|
||||||
return toRef(this.repos, slug).value;
|
return repos.get(slug);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
// setter
|
function setRepo(repo: Repo) {
|
||||||
setRepo(repo: Repo) {
|
repos.set(repoSlug(repo), repo);
|
||||||
this.repos[repoSlug(repo)] = repo;
|
}
|
||||||
},
|
|
||||||
|
|
||||||
// loading
|
async function loadRepo(owner: string, name: string) {
|
||||||
async loadRepo(owner: string, name: string) {
|
|
||||||
const repo = await apiClient.getRepo(owner, name);
|
const repo = await apiClient.getRepo(owner, name);
|
||||||
this.repos[repoSlug(repo)] = repo;
|
repos.set(repoSlug(repo), repo);
|
||||||
return repo;
|
return repo;
|
||||||
},
|
}
|
||||||
async loadRepos() {
|
|
||||||
const repos = await apiClient.getRepoList();
|
async function loadRepos() {
|
||||||
repos.forEach((repo) => {
|
const _ownedRepos = await apiClient.getRepoList();
|
||||||
this.repos[repoSlug(repo.owner, repo.name)] = repo;
|
_ownedRepos.forEach((repo) => {
|
||||||
|
repos.set(repoSlug(repo), repo);
|
||||||
});
|
});
|
||||||
},
|
ownedRepoSlugs.value = _ownedRepos.map((repo) => repoSlug(repo));
|
||||||
},
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
repos,
|
||||||
|
ownedRepos,
|
||||||
|
ownedRepoSlugs,
|
||||||
|
getRepo,
|
||||||
|
setRepo,
|
||||||
|
loadRepo,
|
||||||
|
loadRepos,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,27 +20,17 @@
|
||||||
</Scaffold>
|
</Scaffold>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, onMounted, ref } from 'vue';
|
import { computed, onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import Button from '~/components/atomic/Button.vue';
|
import Button from '~/components/atomic/Button.vue';
|
||||||
import ListItem from '~/components/atomic/ListItem.vue';
|
import ListItem from '~/components/atomic/ListItem.vue';
|
||||||
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
|
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
|
||||||
import { useRepoSearch } from '~/compositions/useRepoSearch';
|
import { useRepoSearch } from '~/compositions/useRepoSearch';
|
||||||
import RepoStore from '~/store/repos';
|
import { useRepoStore } from '~/store/repos';
|
||||||
|
|
||||||
export default defineComponent({
|
const repoStore = useRepoStore();
|
||||||
name: 'Repos',
|
const repos = computed(() => Object.values(repoStore.ownedRepos));
|
||||||
|
|
||||||
components: {
|
|
||||||
Button,
|
|
||||||
ListItem,
|
|
||||||
Scaffold,
|
|
||||||
},
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const repoStore = RepoStore();
|
|
||||||
const repos = computed(() => Object.values(repoStore.repos));
|
|
||||||
const search = ref('');
|
const search = ref('');
|
||||||
|
|
||||||
const { searchedRepos } = useRepoSearch(repos, search);
|
const { searchedRepos } = useRepoSearch(repos, search);
|
||||||
|
@ -48,8 +38,4 @@ export default defineComponent({
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await repoStore.loadRepos();
|
await repoStore.loadRepos();
|
||||||
});
|
});
|
||||||
|
|
||||||
return { searchedRepos, search };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -37,7 +37,7 @@ import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
|
||||||
import useApiClient from '~/compositions/useApiClient';
|
import useApiClient from '~/compositions/useApiClient';
|
||||||
import { useRepoSearch } from '~/compositions/useRepoSearch';
|
import { useRepoSearch } from '~/compositions/useRepoSearch';
|
||||||
import { OrgPermissions } from '~/lib/api/types';
|
import { OrgPermissions } from '~/lib/api/types';
|
||||||
import RepoStore from '~/store/repos';
|
import { useRepoStore } from '~/store/repos';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'ReposOwner',
|
name: 'ReposOwner',
|
||||||
|
@ -57,9 +57,9 @@ export default defineComponent({
|
||||||
|
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const apiClient = useApiClient();
|
const apiClient = useApiClient();
|
||||||
const repoStore = RepoStore();
|
const repoStore = useRepoStore();
|
||||||
// TODO: filter server side
|
// TODO: filter server side
|
||||||
const repos = computed(() => Object.values(repoStore.repos).filter((v) => v.owner === props.repoOwner));
|
const repos = computed(() => Array.from(repoStore.repos.values()).filter((repo) => repo.owner === props.repoOwner));
|
||||||
const search = ref('');
|
const search = ref('');
|
||||||
const orgPermissions = ref<OrgPermissions>({ member: false, admin: false });
|
const orgPermissions = ref<OrgPermissions>({ member: false, admin: false });
|
||||||
|
|
||||||
|
|
|
@ -64,8 +64,8 @@ import useAuthentication from '~/compositions/useAuthentication';
|
||||||
import useConfig from '~/compositions/useConfig';
|
import useConfig from '~/compositions/useConfig';
|
||||||
import useNotifications from '~/compositions/useNotifications';
|
import useNotifications from '~/compositions/useNotifications';
|
||||||
import { RepoPermissions } from '~/lib/api/types';
|
import { RepoPermissions } from '~/lib/api/types';
|
||||||
import PipelineStore from '~/store/pipelines';
|
import { usePipelineStore } from '~/store/pipelines';
|
||||||
import RepoStore from '~/store/repos';
|
import { useRepoStore } from '~/store/repos';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
repoOwner: {
|
repoOwner: {
|
||||||
|
@ -81,8 +81,8 @@ const props = defineProps({
|
||||||
|
|
||||||
const repoOwner = toRef(props, 'repoOwner');
|
const repoOwner = toRef(props, 'repoOwner');
|
||||||
const repoName = toRef(props, 'repoName');
|
const repoName = toRef(props, 'repoName');
|
||||||
const repoStore = RepoStore();
|
const repoStore = useRepoStore();
|
||||||
const pipelineStore = PipelineStore();
|
const pipelineStore = usePipelineStore();
|
||||||
const apiClient = useApiClient();
|
const apiClient = useApiClient();
|
||||||
const notifications = useNotifications();
|
const notifications = useNotifications();
|
||||||
const { isAuthenticated } = useAuthentication();
|
const { isAuthenticated } = useAuthentication();
|
||||||
|
@ -93,7 +93,7 @@ const i18n = useI18n();
|
||||||
const { forge } = useConfig();
|
const { forge } = useConfig();
|
||||||
const repo = repoStore.getRepo(repoOwner, repoName);
|
const repo = repoStore.getRepo(repoOwner, repoName);
|
||||||
const repoPermissions = ref<RepoPermissions>();
|
const repoPermissions = ref<RepoPermissions>();
|
||||||
const pipelines = pipelineStore.getSortedPipelines(repoOwner, repoName);
|
const pipelines = pipelineStore.getRepoPipelines(repoOwner, repoName);
|
||||||
provide('repo', repo);
|
provide('repo', repo);
|
||||||
provide('repo-permissions', repoPermissions);
|
provide('repo-permissions', repoPermissions);
|
||||||
provide('pipelines', pipelines);
|
provide('pipelines', pipelines);
|
||||||
|
@ -121,7 +121,7 @@ async function loadRepo() {
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await pipelineStore.loadPipelines(repoOwner.value, repoName.value);
|
await pipelineStore.loadRepoPipelines(repoOwner.value, repoName.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
@ -132,7 +132,7 @@ watch([repoOwner, repoName], () => {
|
||||||
loadRepo();
|
loadRepo();
|
||||||
});
|
});
|
||||||
|
|
||||||
const badgeUrl = computed(() => `/api/badges/${repo.value.owner}/${repo.value.name}/status.svg`);
|
const badgeUrl = computed(() => repo.value && `/api/badges/${repo.value.owner}/${repo.value.name}/status.svg`);
|
||||||
|
|
||||||
const activeTab = computed({
|
const activeTab = computed({
|
||||||
get() {
|
get() {
|
||||||
|
|
|
@ -92,7 +92,7 @@ import useNotifications from '~/compositions/useNotifications';
|
||||||
import usePipeline from '~/compositions/usePipeline';
|
import usePipeline from '~/compositions/usePipeline';
|
||||||
import { useRouteBackOrDefault } from '~/compositions/useRouteBackOrDefault';
|
import { useRouteBackOrDefault } from '~/compositions/useRouteBackOrDefault';
|
||||||
import { Repo, RepoPermissions } from '~/lib/api/types';
|
import { Repo, RepoPermissions } from '~/lib/api/types';
|
||||||
import PipelineStore from '~/store/pipelines';
|
import { usePipelineStore } from '~/store/pipelines';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'PipelineWrapper',
|
name: 'PipelineWrapper',
|
||||||
|
@ -130,7 +130,7 @@ export default defineComponent({
|
||||||
const favicon = useFavicon();
|
const favicon = useFavicon();
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
|
||||||
const pipelineStore = PipelineStore();
|
const pipelineStore = usePipelineStore();
|
||||||
const pipelineId = toRef(props, 'pipelineId');
|
const pipelineId = toRef(props, 'pipelineId');
|
||||||
const repoOwner = toRef(props, 'repoOwner');
|
const repoOwner = toRef(props, 'repoOwner');
|
||||||
const repoName = toRef(props, 'repoName');
|
const repoName = toRef(props, 'repoName');
|
||||||
|
@ -155,7 +155,7 @@ export default defineComponent({
|
||||||
|
|
||||||
await pipelineStore.loadPipeline(repo.value.owner, repo.value.name, parseInt(pipelineId.value, 10));
|
await pipelineStore.loadPipeline(repo.value.owner, repo.value.name, parseInt(pipelineId.value, 10));
|
||||||
|
|
||||||
favicon.updateStatus(pipeline.value.status);
|
favicon.updateStatus(pipeline.value?.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { doSubmit: cancelPipeline, isLoading: isCancelingPipeline } = useAsyncAction(async () => {
|
const { doSubmit: cancelPipeline, isLoading: isCancelingPipeline } = useAsyncAction(async () => {
|
||||||
|
@ -163,7 +163,7 @@ export default defineComponent({
|
||||||
throw new Error('Unexpected: Repo is undefined');
|
throw new Error('Unexpected: Repo is undefined');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pipeline.value.steps) {
|
if (!pipeline.value?.steps) {
|
||||||
throw new Error('Unexpected: Pipeline steps not loaded');
|
throw new Error('Unexpected: Pipeline steps not loaded');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue