Compare commits

...

17 commits

Author SHA1 Message Date
Anbraten 9c6111ed5c
Merge 64e5f0f13d into c6b2cd8a48 2024-04-28 09:42:58 +00:00
Anbraten 64e5f0f13d Merge remote-tracking branch 'upstream/main' into fix-secrets 2024-04-28 11:42:36 +02:00
renovate[bot] c6b2cd8a48
chore(deps): update node.js to v22 (#3659) 2024-04-28 11:14:03 +02:00
renovate[bot] 325b1b5e57
chore(deps): update dependency trim to v1 (#3658) 2024-04-28 10:50:39 +02:00
Robert Kaussow 4b1ff6d1a7
Compare to pipeline created timestamp while using before/after filter (#3654) 2024-04-28 10:32:31 +02:00
renovate[bot] 2c3cd83402
chore(deps): update dependency got to v14 (#3657) 2024-04-28 10:16:25 +02:00
renovate[bot] a230e88c3a
chore(deps): lock file maintenance (#3656)
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Update | Change |
|---|---|
| lockFileMaintenance | All locks refreshed |

🔧 This Pull Request updates lock files to use the latest dependency
versions.

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - "before 4am" (UTC).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config help](https://togithub.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/woodpecker-ci/woodpecker).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy4zMjEuMiIsInVwZGF0ZWRJblZlciI6IjM3LjMyMS4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiLCJkb2N1bWVudGF0aW9uIiwidWkiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-28 08:18:02 +02:00
Anbraten ae1cfd2ecd cleanup 2024-04-24 15:11:34 +02:00
Anbraten b3f163f15c Merge remote-tracking branch 'upstream/main' into fix-secrets 2024-04-24 14:47:40 +02:00
Anbraten dfb59b92b9 cleanup 2024-04-24 14:46:28 +02:00
Anbraten 5ee4a0f7d3 undo unrelated changes 2024-04-24 14:22:56 +02:00
Anbraten ed9be3e7c8 allow get and list global secrets 2024-04-17 11:42:46 +02:00
Anbraten 3880844a79 improve pagination 2024-04-17 11:37:19 +02:00
Anbraten d316e3c5cb improve tests 2024-04-17 10:57:58 +02:00
Anbraten db5a384390 add tests 2024-04-16 10:05:49 +02:00
Anbraten 8ce3461289 fix secrets list loading 2024-04-16 08:53:12 +02:00
Anbraten cacb16c0f1 fix secret loading 2024-04-16 08:44:56 +02:00
17 changed files with 3796 additions and 3516 deletions

View file

@ -3,7 +3,7 @@ when:
variables:
- &golang_image 'docker.io/golang:1.22.2'
- &node_image 'docker.io/node:21-alpine'
- &node_image 'docker.io/node:22-alpine'
- &xgo_image 'docker.io/techknowlogick/xgo:go-1.22.1'
- &xgo_version 'go-1.21.2'

View file

@ -1,6 +1,6 @@
variables:
- &golang_image 'docker.io/golang:1.22.2'
- &node_image 'docker.io/node:21-alpine'
- &node_image 'docker.io/node:22-alpine'
- &xgo_image 'docker.io/techknowlogick/xgo:go-1.22.1'
- &xgo_version 'go-1.21.2'
- &buildx_plugin 'docker.io/woodpeckerci/plugin-docker-buildx:3.2.1'

View file

@ -13,7 +13,7 @@ steps:
branch: renovate/*
- name: spellcheck
image: docker.io/node:21-alpine
image: docker.io/node:22-alpine
depends_on: []
commands:
- corepack enable

View file

@ -6,7 +6,7 @@ when:
- renovate/*
variables:
- &node_image 'docker.io/node:21-alpine'
- &node_image 'docker.io/node:22-alpine'
- &when
path:
# related config files

View file

@ -7,8 +7,8 @@
</p>
<br/>
<p align="center">
<a href="https://ci.woodpecker-ci.org/repos/3780" title="Build Status">
<img src="https://ci.woodpecker-ci.org/api/badges/3780/status.svg" alt="Build Status">
<a href="https://ci.woodpecker-ci.org/repos/3780" title="Pipeline Status">
<img src="https://ci.woodpecker-ci.org/api/badges/3780/status.svg" alt="Pipeline Status">
</a>
<a href="https://codecov.io/gh/woodpecker-ci/woodpecker">
<img src="https://codecov.io/gh/woodpecker-ci/woodpecker/branch/main/graph/badge.svg" alt="Code coverage">

View file

@ -1,6 +1,6 @@
# docker build --rm -f docker/Dockerfile.make -t woodpecker/make:local .
FROM docker.io/golang:1.22-alpine3.19 as golang_image
FROM docker.io/node:21-alpine3.19
FROM docker.io/node:22-alpine3.19
# renovate: datasource=repology depName=alpine_3_19/make versioning=loose
ENV MAKE_VERSION="4.4.1-r2"

View file

@ -53,8 +53,8 @@
},
"pnpm": {
"overrides": {
"trim": "^0.0.3",
"got": "^11.8.5"
"trim": "^1.0.0",
"got": "^14.0.0"
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -169,12 +169,17 @@ func apiRoutes(e *gin.RouterGroup) {
queue.GET("/norunningpipelines", api.BlockTilQueueHasRunningItem)
}
// global secrets can be read without actual values by any user
readGlobalSecrets := apiBase.Group("/secrets")
{
readGlobalSecrets.Use(session.MustUser())
readGlobalSecrets.GET("", api.GetGlobalSecretList)
readGlobalSecrets.GET("/:secret", api.GetGlobalSecret)
}
secrets := apiBase.Group("/secrets")
{
secrets.Use(session.MustAdmin())
secrets.GET("", api.GetGlobalSecretList)
secrets.POST("", api.PostGlobalSecret)
secrets.GET("/:secret", api.GetGlobalSecret)
secrets.PATCH("/:secret", api.PatchGlobalSecret)
secrets.DELETE("/:secret", api.DeleteGlobalSecret)
}

View file

@ -59,11 +59,11 @@ func (s storage) GetPipelineList(repo *model.Repo, p *model.ListOptions, f *mode
if f != nil {
if f.After != 0 {
cond = cond.And(builder.Gt{"pipeline_started": f.After})
cond = cond.And(builder.Gt{"pipeline_created": f.After})
}
if f.Before != 0 {
cond = cond.And(builder.Lt{"pipeline_started": f.Before})
cond = cond.And(builder.Lt{"pipeline_created": f.Before})
}
}

View file

@ -231,21 +231,19 @@ func TestPipelines(t *testing.T) {
})
g.It("Should get filtered pipelines", func() {
dt1, _ := time.Parse(time.RFC3339, "2023-01-15T15:00:00Z")
pipeline1 := &model.Pipeline{
RepoID: repo.ID,
Started: dt1.Unix(),
RepoID: repo.ID,
}
dt2, _ := time.Parse(time.RFC3339, "2023-01-15T16:30:00Z")
pipeline2 := &model.Pipeline{
RepoID: repo.ID,
Started: dt2.Unix(),
RepoID: repo.ID,
}
err1 := store.CreatePipeline(pipeline1, []*model.Step{}...)
g.Assert(err1).IsNil()
time.Sleep(1 * time.Second)
before := time.Now().Unix()
err2 := store.CreatePipeline(pipeline2, []*model.Step{}...)
g.Assert(err2).IsNil()
pipelines, err3 := store.GetPipelineList(&model.Repo{ID: 1}, &model.ListOptions{Page: 1, PerPage: 50}, &model.PipelineFilter{Before: dt2.Unix()})
pipelines, err3 := store.GetPipelineList(&model.Repo{ID: 1}, &model.ListOptions{Page: 1, PerPage: 50}, &model.PipelineFilter{Before: before})
g.Assert(err3).IsNil()
g.Assert(len(pipelines)).Equal(1)
g.Assert(pipelines[0].ID).Equal(pipeline1.ID)

View file

@ -14,7 +14,7 @@
"format": "prettier --write .",
"format:check": "prettier -c .",
"typecheck": "vue-tsc --noEmit",
"test": "echo 'No tests configured' && exit 0"
"test": "vitest"
},
"dependencies": {
"@intlify/unplugin-vue-i18n": "^4.0.0",
@ -44,6 +44,7 @@
"@typescript-eslint/parser": "^7.0.0",
"@vitejs/plugin-vue": "^5.0.3",
"@vue/compiler-sfc": "^3.4.15",
"@vue/test-utils": "^2.4.5",
"eslint": "^8.56.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-airbnb-typescript": "^18.0.0",
@ -54,6 +55,7 @@
"eslint-plugin-simple-import-sort": "^12.0.0",
"eslint-plugin-vue": "^9.20.1",
"eslint-plugin-vue-scoped-css": "^2.7.2",
"jsdom": "^24.0.0",
"prettier": "^3.2.4",
"replace-in-file": "^7.1.0",
"tinycolor2": "^1.6.0",
@ -64,6 +66,7 @@
"vite-plugin-prismjs": "^0.0.11",
"vite-plugin-windicss": "^1.9.3",
"vite-svg-loader": "^5.1.0",
"vitest": "^1.5.0",
"vue-eslint-parser": "^9.4.0",
"vue-tsc": "^2.0.0",
"windicss": "^3.5.6"

File diff suppressed because it is too large Load diff

View file

@ -83,6 +83,7 @@ async function loadSecrets(page: number, level: 'repo' | 'org' | 'global'): Prom
const { resetPage, data: _secrets } = usePagination(loadSecrets, () => !selectedSecret.value, {
each: ['repo', 'org', 'global'],
pageSize: 50,
});
const secrets = computed(() => {
const secretsList: Record<string, Secret & { edit?: boolean; level: 'repo' | 'org' | 'global' }> = {};

View file

@ -0,0 +1,155 @@
import { shallowMount } from '@vue/test-utils';
import { describe, expect, it } from 'vitest';
import { type Ref, watch } from 'vue';
import { usePagination } from './usePaginate';
async function waitForState<T>(ref: Ref<T>, expected: T): Promise<void> {
await new Promise<void>((resolve) => {
watch(
ref,
(value) => {
if (value === expected) {
resolve();
}
},
{ immediate: true },
);
});
}
// eslint-disable-next-line promise/prefer-await-to-callbacks
export const mountComposition = (cb: () => void) => {
const wrapper = shallowMount({
setup() {
// eslint-disable-next-line promise/prefer-await-to-callbacks
cb();
return {};
},
template: '<div />',
});
return wrapper;
};
describe('usePaginate', () => {
const repoSecrets = [
[{ name: 'repo1' }, { name: 'repo2' }, { name: 'repo3' }],
[{ name: 'repo4' }, { name: 'repo5' }, { name: 'repo6' }],
];
const orgSecrets = [
[{ name: 'org1' }, { name: 'org2' }, { name: 'org3' }],
[{ name: 'org4' }, { name: 'org5' }, { name: 'org6' }],
];
it('should get first page', async () => {
let usePaginationComposition = null as unknown as ReturnType<typeof usePagination>;
mountComposition(() => {
usePaginationComposition = usePagination<{ name: string }>(
async (page) => repoSecrets[page - 1],
() => true,
);
});
await waitForState(usePaginationComposition.loading, true);
await waitForState(usePaginationComposition.loading, false);
expect(usePaginationComposition.data.value.length).toBe(3);
expect(usePaginationComposition.data.value[0]).toStrictEqual(repoSecrets[0][0]);
});
it('should get first & second page', async () => {
let usePaginationComposition = null as unknown as ReturnType<typeof usePagination>;
mountComposition(() => {
usePaginationComposition = usePagination<{ name: string }>(
async (page) => repoSecrets[page - 1],
() => true,
);
});
await waitForState(usePaginationComposition.loading, true);
await waitForState(usePaginationComposition.loading, false);
usePaginationComposition.nextPage();
await waitForState(usePaginationComposition.loading, false);
expect(usePaginationComposition.data.value.length).toBe(6);
expect(usePaginationComposition.data.value.at(-1)).toStrictEqual(repoSecrets[1][2]);
});
it('should get first page for each category', async () => {
let usePaginationComposition = null as unknown as ReturnType<typeof usePagination>;
mountComposition(() => {
usePaginationComposition = usePagination<{ name: string }>(
async (page, level) => {
if (level === 'repo') {
return repoSecrets[page - 1];
}
return orgSecrets[page - 1];
},
() => true,
{ each: ['repo', 'org'] },
);
});
await waitForState(usePaginationComposition.loading, true);
await waitForState(usePaginationComposition.loading, false);
usePaginationComposition.nextPage();
await waitForState(usePaginationComposition.loading, false);
usePaginationComposition.nextPage();
await waitForState(usePaginationComposition.loading, false);
usePaginationComposition.nextPage();
await waitForState(usePaginationComposition.loading, false);
console.log(usePaginationComposition.data.value);
expect(usePaginationComposition.data.value.length).toBe(9);
expect(usePaginationComposition.data.value.at(-1)).toStrictEqual(orgSecrets[0][2]);
});
it('should reset page and get first page again', async () => {
let usePaginationComposition = null as unknown as ReturnType<typeof usePagination>;
mountComposition(() => {
usePaginationComposition = usePagination<{ name: string }>(
async (page) => repoSecrets[page - 1],
() => true,
);
});
await waitForState(usePaginationComposition.loading, true);
await waitForState(usePaginationComposition.loading, false);
usePaginationComposition.nextPage();
await waitForState(usePaginationComposition.loading, false);
usePaginationComposition.resetPage();
await waitForState(usePaginationComposition.loading, false);
expect(usePaginationComposition.data.value.length).toBe(3);
expect(usePaginationComposition.data.value[0]).toStrictEqual(repoSecrets[0][0]);
});
it('should not hasMore when no data is left', async () => {
let usePaginationComposition = null as unknown as ReturnType<typeof usePagination>;
mountComposition(() => {
usePaginationComposition = usePagination<{ name: string }>(
async (page) => repoSecrets[page - 1],
() => true,
);
});
await waitForState(usePaginationComposition.loading, true);
await waitForState(usePaginationComposition.loading, false);
expect(usePaginationComposition.hasMore.value).toBe(true);
expect(usePaginationComposition.data.value.length).toBe(3);
usePaginationComposition.nextPage();
await waitForState(usePaginationComposition.loading, false);
expect(usePaginationComposition.hasMore.value).toBe(true);
expect(usePaginationComposition.data.value.length).toBe(6);
usePaginationComposition.nextPage();
await waitForState(usePaginationComposition.loading, false);
expect(usePaginationComposition.hasMore.value).toBe(false);
expect(usePaginationComposition.data.value.length).toBe(6);
});
});

View file

@ -18,15 +18,19 @@ export async function usePaginate<T>(getSingle: (page: number) => Promise<T[]>):
export function usePagination<T, S = unknown>(
_loadData: (page: number, arg: S) => Promise<T[] | null>,
isActive: () => boolean = () => true,
{ scrollElement: _scrollElement, each: _each }: { scrollElement?: Ref<HTMLElement | null>; each?: S[] } = {},
{
scrollElement: _scrollElement,
each: _each,
pageSize: _pageSize,
}: { scrollElement?: Ref<HTMLElement | null> | null; each?: S[]; pageSize?: number } = {},
) {
const scrollElement = _scrollElement ?? ref(document.getElementById('scroll-component'));
const scrollElement = _scrollElement === null ? null : ref(document.getElementById('scroll-component'));
const page = ref(1);
const pageSize = ref(0);
const pageSize = ref(_pageSize ?? 0);
const hasMore = ref(true);
const data = ref<T[]>([]) as Ref<T[]>;
const loading = ref(false);
const each = ref(_each ?? []);
const each = ref([...(_each ?? [])]);
async function loadData() {
if (loading.value === true || hasMore.value === false) {
@ -45,7 +49,7 @@ export function usePagination<T, S = unknown>(
// use next each element
each.value.shift();
page.value = 1;
pageSize.value = 0;
pageSize.value = _pageSize ?? 0;
hasMore.value = each.value.length > 0;
if (hasMore.value) {
loading.value = false;
@ -65,15 +69,19 @@ export function usePagination<T, S = unknown>(
}
}
useInfiniteScroll(scrollElement, nextPage, { distance: 10 });
if (scrollElement !== null) {
useInfiniteScroll(scrollElement, nextPage, { distance: 10 });
}
async function resetPage() {
const _page = page.value;
page.value = 1;
pageSize.value = _pageSize ?? 0;
hasMore.value = true;
data.value = [];
each.value = (_each ?? []) as UnwrapRef<S[]>;
page.value = 1;
loading.value = false;
each.value = [...(_each ?? [])] as UnwrapRef<S[]>;
if (_page === 1) {
// we need to reload manually as the page is already 1, so changing won't trigger watcher

View file

@ -7,10 +7,10 @@ import replace from 'replace-in-file';
import IconsResolver from 'unplugin-icons/resolver';
import Icons from 'unplugin-icons/vite';
import Components from 'unplugin-vue-components/vite';
import { defineConfig } from 'vite';
import prismjs from 'vite-plugin-prismjs';
import WindiCSS from 'vite-plugin-windicss';
import svgLoader from 'vite-svg-loader';
import { defineConfig } from 'vitest/config';
function woodpeckerInfoPlugin() {
return {
@ -133,4 +133,8 @@ export default defineConfig({
host: process.env.VITE_DEV_SERVER_HOST || '127.0.0.1',
port: 8010,
},
test: {
globals: true,
environment: 'jsdom',
},
});