firstly 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/LICENSE +18 -0
- package/README.md +12 -0
- package/esm/KitBaseEnum.d.ts +35 -0
- package/esm/KitBaseEnum.js +32 -0
- package/esm/KitEntity.d.ts +2 -0
- package/esm/KitEntity.js +24 -0
- package/esm/KitFields.d.ts +10 -0
- package/esm/KitFields.js +196 -0
- package/esm/ROUTES.d.ts +88 -0
- package/esm/ROUTES.js +98 -0
- package/esm/SqlDatabase/LogToConsoleCustom.d.ts +1 -0
- package/esm/SqlDatabase/LogToConsoleCustom.js +102 -0
- package/esm/api/index.d.ts +42 -0
- package/esm/api/index.js +97 -0
- package/esm/auth/Adapter.d.ts +10 -0
- package/esm/auth/Adapter.js +54 -0
- package/esm/auth/AuthController.d.ts +59 -0
- package/esm/auth/AuthController.js +434 -0
- package/esm/auth/Entities.d.ts +39 -0
- package/esm/auth/Entities.js +154 -0
- package/esm/auth/RoleController.d.ts +14 -0
- package/esm/auth/RoleController.js +57 -0
- package/esm/auth/helper.d.ts +1 -0
- package/esm/auth/helper.js +7 -0
- package/esm/auth/index.d.ts +153 -0
- package/esm/auth/index.js +279 -0
- package/esm/auth/providers/github.d.ts +25 -0
- package/esm/auth/providers/github.js +51 -0
- package/esm/auth/providers/index.d.ts +3 -0
- package/esm/auth/providers/index.js +26 -0
- package/esm/auth/providers/strava.d.ts +25 -0
- package/esm/auth/providers/strava.js +51 -0
- package/esm/auth/static/assets/Page-BMFREPjF.d.ts +5 -0
- package/esm/auth/static/assets/Page-BMFREPjF.js +18 -0
- package/esm/auth/static/assets/Page-BMOLAIFx.d.ts +5 -0
- package/esm/auth/static/assets/Page-BMOLAIFx.js +1 -0
- package/esm/auth/static/assets/Page-BwHye0GW.d.ts +5 -0
- package/esm/auth/static/assets/Page-BwHye0GW.js +1 -0
- package/esm/auth/static/assets/Page-gV58jf2r.css +1 -0
- package/esm/auth/static/assets/index-CKmKKRRL.d.ts +53 -0
- package/esm/auth/static/assets/index-CKmKKRRL.js +2 -0
- package/esm/auth/static/assets/index-R27C_TlP.css +4 -0
- package/esm/auth/static/favicon.svg +79 -0
- package/esm/auth/static/index.html +14 -0
- package/esm/auth/types.d.ts +33 -0
- package/esm/auth/types.js +1 -0
- package/esm/bin/cmd.d.ts +1 -0
- package/esm/bin/cmd.js +408 -0
- package/esm/changeLog/index.d.ts +55 -0
- package/esm/changeLog/index.js +179 -0
- package/esm/cron/index.d.ts +60 -0
- package/esm/cron/index.js +102 -0
- package/esm/feedback/FeedbackController.d.ts +30 -0
- package/esm/feedback/FeedbackController.js +313 -0
- package/esm/feedback/index.d.ts +18 -0
- package/esm/feedback/index.js +14 -0
- package/esm/feedback/ui/DialogIssue.svelte +102 -0
- package/esm/feedback/ui/DialogIssue.svelte.d.ts +20 -0
- package/esm/feedback/ui/DialogIssues.svelte +91 -0
- package/esm/feedback/ui/DialogIssues.svelte.d.ts +20 -0
- package/esm/feedback/ui/DialogMilestones.svelte +38 -0
- package/esm/feedback/ui/DialogMilestones.svelte.d.ts +18 -0
- package/esm/feedback/ui/Feedback.svelte +12 -0
- package/esm/feedback/ui/Feedback.svelte.d.ts +16 -0
- package/esm/formats/dates.d.ts +18 -0
- package/esm/formats/dates.js +35 -0
- package/esm/formats/index.d.ts +4 -0
- package/esm/formats/index.js +3 -0
- package/esm/formats/numbers.d.ts +4 -0
- package/esm/formats/numbers.js +34 -0
- package/esm/formats/strings.d.ts +11 -0
- package/esm/formats/strings.js +109 -0
- package/esm/handle/index.d.ts +7 -0
- package/esm/handle/index.js +40 -0
- package/esm/helper.d.ts +50 -0
- package/esm/helper.js +118 -0
- package/esm/index.d.ts +103 -0
- package/esm/index.js +42 -0
- package/esm/kitCellsBuildor.d.ts +45 -0
- package/esm/kitCellsBuildor.js +105 -0
- package/esm/kitStoreItem.d.ts +28 -0
- package/esm/kitStoreItem.js +170 -0
- package/esm/kitStoreList.d.ts +33 -0
- package/esm/kitStoreList.js +98 -0
- package/esm/mail/index.d.ts +11 -0
- package/esm/mail/index.js +51 -0
- package/esm/theme.d.ts +4 -0
- package/esm/theme.js +4 -0
- package/esm/ui/Button.svelte +102 -0
- package/esm/ui/Button.svelte.d.ts +27 -0
- package/esm/ui/Clipboardable.svelte +19 -0
- package/esm/ui/Clipboardable.svelte.d.ts +25 -0
- package/esm/ui/Field.svelte +288 -0
- package/esm/ui/Field.svelte.d.ts +29 -0
- package/esm/ui/FieldGroup.svelte +91 -0
- package/esm/ui/FieldGroup.svelte.d.ts +30 -0
- package/esm/ui/Grid.svelte +246 -0
- package/esm/ui/Grid.svelte.d.ts +46 -0
- package/esm/ui/GridLoading.svelte +32 -0
- package/esm/ui/GridLoading.svelte.d.ts +20 -0
- package/esm/ui/GridPaginate.svelte +66 -0
- package/esm/ui/GridPaginate.svelte.d.ts +22 -0
- package/esm/ui/Icon.svelte +86 -0
- package/esm/ui/Icon.svelte.d.ts +46 -0
- package/esm/ui/LibIcon.d.ts +23 -0
- package/esm/ui/LibIcon.js +28 -0
- package/esm/ui/Loading.svelte +11 -0
- package/esm/ui/Loading.svelte.d.ts +20 -0
- package/esm/ui/Tooltip.svelte +42 -0
- package/esm/ui/Tooltip.svelte.d.ts +22 -0
- package/esm/ui/dialog/DialogForm.svelte +70 -0
- package/esm/ui/dialog/DialogForm.svelte.d.ts +19 -0
- package/esm/ui/dialog/DialogManagement.svelte +87 -0
- package/esm/ui/dialog/DialogManagement.svelte.d.ts +25 -0
- package/esm/ui/dialog/DialogPrimitive.svelte +89 -0
- package/esm/ui/dialog/DialogPrimitive.svelte.d.ts +28 -0
- package/esm/ui/dialog/FormEditAction.svelte +54 -0
- package/esm/ui/dialog/FormEditAction.svelte.d.ts +24 -0
- package/esm/ui/dialog/dialog.d.ts +51 -0
- package/esm/ui/dialog/dialog.js +98 -0
- package/esm/ui/index.d.ts +5 -0
- package/esm/ui/index.js +19 -0
- package/esm/ui/internals/FieldContainer.svelte +22 -0
- package/esm/ui/internals/FieldContainer.svelte.d.ts +30 -0
- package/esm/ui/internals/Input.svelte +98 -0
- package/esm/ui/internals/Input.svelte.d.ts +35 -0
- package/esm/ui/internals/Textarea.svelte +61 -0
- package/esm/ui/internals/Textarea.svelte.d.ts +30 -0
- package/esm/ui/internals/select/MultiSelectMelt.svelte +217 -0
- package/esm/ui/internals/select/MultiSelectMelt.svelte.d.ts +30 -0
- package/esm/ui/internals/select/SelectMelt.svelte +238 -0
- package/esm/ui/internals/select/SelectMelt.svelte.d.ts +35 -0
- package/esm/ui/internals/select/SelectRadio.svelte +37 -0
- package/esm/ui/internals/select/SelectRadio.svelte.d.ts +25 -0
- package/esm/ui/link/Link.svelte +28 -0
- package/esm/ui/link/Link.svelte.d.ts +25 -0
- package/esm/ui/link/LinkPlus.svelte +44 -0
- package/esm/ui/link/LinkPlus.svelte.d.ts +21 -0
- package/esm/utils/tailwind.d.ts +2 -0
- package/esm/utils/tailwind.js +3 -0
- package/esm/utils/transition.d.ts +10 -0
- package/esm/utils/transition.js +33 -0
- package/esm/utils/types.d.ts +17 -0
- package/esm/utils/types.js +17 -0
- package/esm/virtual/Customer.d.ts +4 -0
- package/esm/virtual/Customer.js +24 -0
- package/esm/virtual/FilterEntity.d.ts +7 -0
- package/esm/virtual/FilterEntity.js +34 -0
- package/esm/virtual/StateDemoEnum.d.ts +9 -0
- package/esm/virtual/StateDemoEnum.js +42 -0
- package/esm/virtual/UIEntity.d.ts +16 -0
- package/esm/virtual/UIEntity.js +84 -0
- package/esm/vite/index.d.ts +8 -0
- package/esm/vite/index.js +47 -0
- package/package.json +94 -10
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { CronJob } from 'cron';
|
|
2
|
+
import { green, Log, magenta, red, yellow } from '@kitql/helpers';
|
|
3
|
+
const log = new Log('firstly | cron');
|
|
4
|
+
export const jobs = {};
|
|
5
|
+
/**
|
|
6
|
+
* Link to a nice Cheatsheet TODO
|
|
7
|
+
*/
|
|
8
|
+
export const cronTime = {
|
|
9
|
+
/**
|
|
10
|
+
* Every morning is actually at 4 am and 7 minutes. (because I like this number!)
|
|
11
|
+
*/
|
|
12
|
+
every_morning: '0 7 4 * * *',
|
|
13
|
+
/**
|
|
14
|
+
* Every second
|
|
15
|
+
*/
|
|
16
|
+
every_second: '* * * * * *',
|
|
17
|
+
/**
|
|
18
|
+
* Every minute
|
|
19
|
+
*/
|
|
20
|
+
every_minute: '0 * * * * *',
|
|
21
|
+
/**
|
|
22
|
+
* Every 10 minute
|
|
23
|
+
*/
|
|
24
|
+
every_10_minute: '*/10 * * * *',
|
|
25
|
+
/**
|
|
26
|
+
* Every friday at 5:11 am
|
|
27
|
+
*/
|
|
28
|
+
every_friday_morning: '11 5 * * 5',
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* usage:
|
|
32
|
+
*
|
|
33
|
+
* ```ts
|
|
34
|
+
* import { cron, cronTime } from 'firstly/cron'
|
|
35
|
+
*
|
|
36
|
+
* export const api = firstly({
|
|
37
|
+
* modules: [
|
|
38
|
+
* cron([{
|
|
39
|
+
* topic: 'first_cron',
|
|
40
|
+
* cronTime: cronTime.every_second,
|
|
41
|
+
* onTick: () => { console.log('hello') },
|
|
42
|
+
* start: !dev, // Start in production
|
|
43
|
+
* // runOnInit: dev, // nice in dev environement
|
|
44
|
+
* }])
|
|
45
|
+
* ]
|
|
46
|
+
* })
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* using [cron](https://www.npmjs.com/package/cron) library under the hood
|
|
50
|
+
*/
|
|
51
|
+
export const cron = (jobsInfos) => {
|
|
52
|
+
return {
|
|
53
|
+
name: 'cron',
|
|
54
|
+
initApi: async () => {
|
|
55
|
+
jobsInfos.forEach((infos) => {
|
|
56
|
+
const { topic, runOnInit, logs, concurrent, ...params } = infos;
|
|
57
|
+
const concurrentToUse = concurrent ?? 1;
|
|
58
|
+
const onTickSaved = params.onTick;
|
|
59
|
+
const fullOnTick = async () => {
|
|
60
|
+
if (jobs[topic].concurrentInProgress < concurrentToUse) {
|
|
61
|
+
jobs[topic].concurrentInProgress = jobs[topic].concurrentInProgress + 1;
|
|
62
|
+
if (logs?.starting === undefined || logs?.starting === true) {
|
|
63
|
+
logJobs(topic, job, 'starting...', false, false);
|
|
64
|
+
}
|
|
65
|
+
// @ts-ignore
|
|
66
|
+
await onTickSaved();
|
|
67
|
+
if (logs?.ended === undefined || logs?.ended === true) {
|
|
68
|
+
logJobs(topic, job, 'done');
|
|
69
|
+
}
|
|
70
|
+
jobs[topic].concurrentInProgress = jobs[topic].concurrentInProgress - 1;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
logJobs(topic, job, `skipped because of concurrent limit (${yellow(concurrentToUse.toString())})`, false, false);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
params.onTick = fullOnTick;
|
|
77
|
+
const job = CronJob.from(params);
|
|
78
|
+
jobs[topic] = { job, concurrentInProgress: 0 };
|
|
79
|
+
logJobs(topic, job, 'setup done');
|
|
80
|
+
// If not it will be done too early
|
|
81
|
+
if (runOnInit) {
|
|
82
|
+
job.fireOnTick();
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
const logJobs = (topic, job, message, with_metadata = true, isSuccess = true) => {
|
|
89
|
+
const l = [];
|
|
90
|
+
l.push(magenta(`[${topic}]`));
|
|
91
|
+
l.push(message);
|
|
92
|
+
if (with_metadata) {
|
|
93
|
+
// If the job is "stopped", there will still be a next date, but it will not fire it. The job has to start.
|
|
94
|
+
l.push(`(${job.running ? green('running') : red('stopped')}, next at ${yellow(job.nextDate().toISO())})`);
|
|
95
|
+
}
|
|
96
|
+
if (isSuccess) {
|
|
97
|
+
log.success(l.join(' '));
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
log.info(l.join(' '));
|
|
101
|
+
}
|
|
102
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export declare class FeedbackController {
|
|
2
|
+
static getMilestones(): Promise<{
|
|
3
|
+
title: string;
|
|
4
|
+
id: string;
|
|
5
|
+
number: number;
|
|
6
|
+
}[]>;
|
|
7
|
+
static getIssues(milestoneNumber: number, issueState: 'OPEN' | 'CLOSED'): Promise<{
|
|
8
|
+
id: string;
|
|
9
|
+
number: number;
|
|
10
|
+
titleHTML: string;
|
|
11
|
+
state: 'OPEN' | 'CLOSED';
|
|
12
|
+
}[]>;
|
|
13
|
+
static getIssue(issueNumber: number): Promise<{
|
|
14
|
+
id: any;
|
|
15
|
+
state: any;
|
|
16
|
+
items: {
|
|
17
|
+
bodyHTML: string;
|
|
18
|
+
who?: string | undefined;
|
|
19
|
+
createdAt: Date;
|
|
20
|
+
public: boolean;
|
|
21
|
+
}[];
|
|
22
|
+
}>;
|
|
23
|
+
static createIssue(milestoneId: string, title: string, body: string, page: string): Promise<{
|
|
24
|
+
id: string;
|
|
25
|
+
number: number;
|
|
26
|
+
}>;
|
|
27
|
+
static addCommentOnIssue(issueId: string, body: string, page: string): Promise<string>;
|
|
28
|
+
static close(issueId: string): Promise<string>;
|
|
29
|
+
static reOpen(issueId: string): Promise<string>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { Allow, BackendMethod, remult } from 'remult';
|
|
8
|
+
import { stry } from '@kitql/helpers';
|
|
9
|
+
import { FEEDBACK_OPTIONS } from '.';
|
|
10
|
+
const GITHUB_GRAPHQL_ENDPOINT = 'https://api.github.com/graphql';
|
|
11
|
+
async function getGitHub(query, variables) {
|
|
12
|
+
if (!FEEDBACK_OPTIONS.GITHUB_API_TOKEN) {
|
|
13
|
+
throw new Error('GITHUB_API_TOKEN not found in .env');
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const headers = new Headers({
|
|
17
|
+
Authorization: 'Bearer ' + FEEDBACK_OPTIONS.GITHUB_API_TOKEN,
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
});
|
|
20
|
+
const body = stry({ query, variables }, 0);
|
|
21
|
+
const response = await fetch(GITHUB_GRAPHQL_ENDPOINT, { method: 'POST', headers, body });
|
|
22
|
+
const result = await response.json();
|
|
23
|
+
if (result.errors) {
|
|
24
|
+
/* eslint-disable */
|
|
25
|
+
console.error(`result ERRORS`, body, stry(result));
|
|
26
|
+
}
|
|
27
|
+
return result.data;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
/* eslint-disable */
|
|
31
|
+
console.error(`error`, error);
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
async function addMetaData(issueId, author, page) {
|
|
36
|
+
const commentToMinimize = await getGitHub(`mutation AddComment($input: AddCommentInput!) {
|
|
37
|
+
addComment(input: $input) {
|
|
38
|
+
commentEdge {
|
|
39
|
+
node {
|
|
40
|
+
id
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
`, {
|
|
46
|
+
input: {
|
|
47
|
+
subjectId: issueId,
|
|
48
|
+
body: `<pre>\n${JSON.stringify({ author, page }, null, 2)}\n</pre>`,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
await getGitHub(`mutation MinimizeComment($input: MinimizeCommentInput!) {
|
|
52
|
+
minimizeComment(input: $input) {
|
|
53
|
+
minimizedComment {
|
|
54
|
+
isMinimized
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
`, {
|
|
59
|
+
input: {
|
|
60
|
+
subjectId: commentToMinimize.addComment.commentEdge.node.id,
|
|
61
|
+
classifier: 'OFF_TOPIC',
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
export class FeedbackController {
|
|
66
|
+
static async getMilestones() {
|
|
67
|
+
const data = await getGitHub(`query Milestones(
|
|
68
|
+
$repository: String!
|
|
69
|
+
$owner: String!
|
|
70
|
+
$filter: String
|
|
71
|
+
$take: Int = 25
|
|
72
|
+
$cursor: String
|
|
73
|
+
) {
|
|
74
|
+
repository(name: $repository, owner: $owner) {
|
|
75
|
+
milestones(query: $filter, last: $take, after: $cursor, states: OPEN) {
|
|
76
|
+
pageInfo {
|
|
77
|
+
endCursor
|
|
78
|
+
}
|
|
79
|
+
nodes {
|
|
80
|
+
id
|
|
81
|
+
number
|
|
82
|
+
title
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
`, {
|
|
88
|
+
repository: FEEDBACK_OPTIONS.repo.name,
|
|
89
|
+
owner: FEEDBACK_OPTIONS.repo.owner,
|
|
90
|
+
filter: FEEDBACK_OPTIONS.milestones?.title_filter ?? '',
|
|
91
|
+
});
|
|
92
|
+
return data.repository.milestones.nodes.map((c) => {
|
|
93
|
+
return {
|
|
94
|
+
...c,
|
|
95
|
+
title: c.title.replaceAll(FEEDBACK_OPTIONS.milestones?.title_filter ?? '', '').trim(),
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
static async getIssues(milestoneNumber, issueState) {
|
|
100
|
+
const issueOrder = issueState === 'CLOSED'
|
|
101
|
+
? { field: 'UPDATED_AT', direction: 'DESC' } // When close, the last issue updated.
|
|
102
|
+
: null; // When open take milestone order
|
|
103
|
+
const data = await getGitHub(`query Issues(
|
|
104
|
+
$repository: String!
|
|
105
|
+
$owner: String!
|
|
106
|
+
$filters: IssueFilters
|
|
107
|
+
$milestoneNumber: Int!
|
|
108
|
+
$take: Int = 25
|
|
109
|
+
$cursor: String
|
|
110
|
+
$issueOrder: IssueOrder
|
|
111
|
+
) {
|
|
112
|
+
repository(name: $repository, owner: $owner) {
|
|
113
|
+
milestone(number: $milestoneNumber) {
|
|
114
|
+
issues(first: $take, after: $cursor, filterBy: $filters, orderBy: $issueOrder) {
|
|
115
|
+
nodes {
|
|
116
|
+
id
|
|
117
|
+
number
|
|
118
|
+
titleHTML
|
|
119
|
+
state
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
`, {
|
|
126
|
+
repository: FEEDBACK_OPTIONS.repo.name,
|
|
127
|
+
owner: FEEDBACK_OPTIONS.repo.owner,
|
|
128
|
+
milestoneNumber,
|
|
129
|
+
filters: {
|
|
130
|
+
labels: FEEDBACK_OPTIONS.milestones?.labels_filters ?? [],
|
|
131
|
+
states: [issueState],
|
|
132
|
+
},
|
|
133
|
+
issueOrder,
|
|
134
|
+
});
|
|
135
|
+
return data.repository.milestone.issues.nodes;
|
|
136
|
+
}
|
|
137
|
+
static async getIssue(issueNumber) {
|
|
138
|
+
const data = await getGitHub(`query Issue($repository: String!, $owner: String!, $issueNumber: Int!) {
|
|
139
|
+
repository(name: $repository, owner: $owner) {
|
|
140
|
+
issue(number: $issueNumber) {
|
|
141
|
+
id
|
|
142
|
+
createdAt
|
|
143
|
+
bodyHTML
|
|
144
|
+
state
|
|
145
|
+
comments(first: 100) {
|
|
146
|
+
nodes {
|
|
147
|
+
id
|
|
148
|
+
isMinimized
|
|
149
|
+
createdAt
|
|
150
|
+
body
|
|
151
|
+
bodyHTML
|
|
152
|
+
reactionGroups {
|
|
153
|
+
content
|
|
154
|
+
reactors(first: 1) {
|
|
155
|
+
totalCount
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
`, {
|
|
164
|
+
repository: FEEDBACK_OPTIONS.repo.name,
|
|
165
|
+
owner: FEEDBACK_OPTIONS.repo.owner,
|
|
166
|
+
issueNumber,
|
|
167
|
+
});
|
|
168
|
+
const items = [];
|
|
169
|
+
const firstItem = {
|
|
170
|
+
bodyHTML: data.repository.issue.bodyHTML,
|
|
171
|
+
createdAt: data.repository.issue.createdAt,
|
|
172
|
+
public: true,
|
|
173
|
+
};
|
|
174
|
+
items.push(firstItem);
|
|
175
|
+
const comments = data.repository.issue.comments.nodes;
|
|
176
|
+
for (let i = 0; i < comments.length; i++) {
|
|
177
|
+
if (comments[i].isMinimized) {
|
|
178
|
+
const parsed = JSON.parse(comments[i].body.replaceAll('<pre>\n', '').replaceAll('\n</pre>', ''));
|
|
179
|
+
items[items.length - 1].who = parsed.author;
|
|
180
|
+
items[items.length - 1].public = true;
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
const nbEye = comments[i].reactionGroups.find((c) => c.content === 'EYES')?.reactors
|
|
184
|
+
.totalCount;
|
|
185
|
+
items.push({
|
|
186
|
+
bodyHTML: comments[i].bodyHTML,
|
|
187
|
+
createdAt: new Date(comments[i].createdAt),
|
|
188
|
+
public: nbEye && nbEye > 0 ? true : false,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const toRet = {
|
|
193
|
+
id: data.repository.issue.id,
|
|
194
|
+
state: data.repository.issue.state,
|
|
195
|
+
items: items.filter((c) => c.public),
|
|
196
|
+
};
|
|
197
|
+
return toRet;
|
|
198
|
+
}
|
|
199
|
+
static async createIssue(milestoneId, title, body, page) {
|
|
200
|
+
const repoInfo = await getGitHub(`query RepoInfo(
|
|
201
|
+
$repository: String!
|
|
202
|
+
$owner: String!
|
|
203
|
+
) {
|
|
204
|
+
repository(name: $repository, owner: $owner) {
|
|
205
|
+
id
|
|
206
|
+
labels(first: 25){
|
|
207
|
+
nodes{
|
|
208
|
+
id
|
|
209
|
+
name
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}`, {
|
|
214
|
+
repository: FEEDBACK_OPTIONS.repo.name,
|
|
215
|
+
owner: FEEDBACK_OPTIONS.repo.owner,
|
|
216
|
+
});
|
|
217
|
+
const repoInfoData = repoInfo.repository;
|
|
218
|
+
const create_label = repoInfoData.labels.nodes.find((c) => c.name === FEEDBACK_OPTIONS.create_label);
|
|
219
|
+
const newIssue = await getGitHub(`mutation CreateIssue($input: CreateIssueInput!) {
|
|
220
|
+
createIssue(input: $input) {
|
|
221
|
+
issue {
|
|
222
|
+
id
|
|
223
|
+
number
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
`, {
|
|
228
|
+
input: {
|
|
229
|
+
repositoryId: repoInfoData.id,
|
|
230
|
+
milestoneId: milestoneId,
|
|
231
|
+
labelIds: [create_label?.id],
|
|
232
|
+
title: title ?? 'New Feedback (wo title...)',
|
|
233
|
+
body: body,
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
const toRet = newIssue.createIssue.issue;
|
|
237
|
+
await addMetaData(toRet.id, remult.user?.name, page);
|
|
238
|
+
return toRet;
|
|
239
|
+
}
|
|
240
|
+
static async addCommentOnIssue(issueId, body, page) {
|
|
241
|
+
const input = {
|
|
242
|
+
subjectId: issueId,
|
|
243
|
+
body,
|
|
244
|
+
};
|
|
245
|
+
await getGitHub(`mutation AddComment($input: AddCommentInput!) {
|
|
246
|
+
addComment(input: $input) {
|
|
247
|
+
commentEdge {
|
|
248
|
+
node {
|
|
249
|
+
id
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
`, {
|
|
255
|
+
input,
|
|
256
|
+
});
|
|
257
|
+
await addMetaData(issueId, remult.user?.name, page);
|
|
258
|
+
return 'done';
|
|
259
|
+
}
|
|
260
|
+
static async close(issueId) {
|
|
261
|
+
const input = {
|
|
262
|
+
issueId,
|
|
263
|
+
};
|
|
264
|
+
await getGitHub(`mutation CloseIssue($input: CloseIssueInput!) {
|
|
265
|
+
closeIssue(input: $input) {
|
|
266
|
+
issue {
|
|
267
|
+
id
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
`, {
|
|
272
|
+
input,
|
|
273
|
+
});
|
|
274
|
+
return 'done';
|
|
275
|
+
}
|
|
276
|
+
static async reOpen(issueId) {
|
|
277
|
+
const input = {
|
|
278
|
+
issueId,
|
|
279
|
+
};
|
|
280
|
+
await getGitHub(`mutation ReOpenIssue($input: ReopenIssueInput!) {
|
|
281
|
+
reopenIssue(input: $input) {
|
|
282
|
+
issue {
|
|
283
|
+
id
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
`, {
|
|
288
|
+
input,
|
|
289
|
+
});
|
|
290
|
+
return 'done';
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
__decorate([
|
|
294
|
+
BackendMethod({ allowed: Allow.authenticated })
|
|
295
|
+
], FeedbackController, "getMilestones", null);
|
|
296
|
+
__decorate([
|
|
297
|
+
BackendMethod({ allowed: Allow.authenticated })
|
|
298
|
+
], FeedbackController, "getIssues", null);
|
|
299
|
+
__decorate([
|
|
300
|
+
BackendMethod({ allowed: Allow.authenticated })
|
|
301
|
+
], FeedbackController, "getIssue", null);
|
|
302
|
+
__decorate([
|
|
303
|
+
BackendMethod({ allowed: Allow.authenticated })
|
|
304
|
+
], FeedbackController, "createIssue", null);
|
|
305
|
+
__decorate([
|
|
306
|
+
BackendMethod({ allowed: Allow.authenticated })
|
|
307
|
+
], FeedbackController, "addCommentOnIssue", null);
|
|
308
|
+
__decorate([
|
|
309
|
+
BackendMethod({ allowed: Allow.authenticated })
|
|
310
|
+
], FeedbackController, "close", null);
|
|
311
|
+
__decorate([
|
|
312
|
+
BackendMethod({ allowed: Allow.authenticated })
|
|
313
|
+
], FeedbackController, "reOpen", null);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Module } from '../api';
|
|
2
|
+
import { FeedbackController } from './FeedbackController';
|
|
3
|
+
import { default as Feedback } from './ui/Feedback.svelte';
|
|
4
|
+
export { FeedbackController, Feedback };
|
|
5
|
+
type FeedbackOptions = {
|
|
6
|
+
GITHUB_API_TOKEN: string;
|
|
7
|
+
repo: {
|
|
8
|
+
owner: string;
|
|
9
|
+
name: string;
|
|
10
|
+
};
|
|
11
|
+
milestones?: {
|
|
12
|
+
title_filter?: string;
|
|
13
|
+
labels_filters?: string[];
|
|
14
|
+
};
|
|
15
|
+
create_label?: string;
|
|
16
|
+
};
|
|
17
|
+
export declare let FEEDBACK_OPTIONS: FeedbackOptions;
|
|
18
|
+
export declare const feedback: (o: FeedbackOptions) => Module;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { FeedbackController } from './FeedbackController';
|
|
2
|
+
import { default as Feedback } from './ui/Feedback.svelte';
|
|
3
|
+
export { FeedbackController, Feedback };
|
|
4
|
+
export let FEEDBACK_OPTIONS = {
|
|
5
|
+
GITHUB_API_TOKEN: '',
|
|
6
|
+
repo: { owner: '', name: '' },
|
|
7
|
+
};
|
|
8
|
+
export const feedback = (o) => {
|
|
9
|
+
FEEDBACK_OPTIONS = o;
|
|
10
|
+
return {
|
|
11
|
+
name: 'feedback',
|
|
12
|
+
controllers: [FeedbackController],
|
|
13
|
+
};
|
|
14
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<script>import { onMount } from "svelte";
|
|
2
|
+
import { repo } from "remult";
|
|
3
|
+
import { page } from "$app/stores";
|
|
4
|
+
import { FeedbackController } from "..";
|
|
5
|
+
import { Button, Field, FilterEntity, kitCellBuildor, Loading } from "../..";
|
|
6
|
+
import Textarea from "../../ui/internals/Textarea.svelte";
|
|
7
|
+
export let dialogId;
|
|
8
|
+
const rmvWarning = dialogId;
|
|
9
|
+
export let milestoneId;
|
|
10
|
+
export let issueNumber;
|
|
11
|
+
let state = "loading";
|
|
12
|
+
let issue;
|
|
13
|
+
const update = async () => {
|
|
14
|
+
state = "loading";
|
|
15
|
+
if (issueNumber) {
|
|
16
|
+
issue = await FeedbackController.getIssue(issueNumber);
|
|
17
|
+
}
|
|
18
|
+
state = "done";
|
|
19
|
+
};
|
|
20
|
+
onMount(async () => {
|
|
21
|
+
await update();
|
|
22
|
+
});
|
|
23
|
+
let title;
|
|
24
|
+
let content;
|
|
25
|
+
const send = async () => {
|
|
26
|
+
state = "loading";
|
|
27
|
+
const p = $page.url.pathname + $page.url.search;
|
|
28
|
+
if (!issue?.id) {
|
|
29
|
+
const result = await FeedbackController.createIssue(milestoneId, title, content, p);
|
|
30
|
+
issueNumber = result.number;
|
|
31
|
+
} else {
|
|
32
|
+
await FeedbackController.addCommentOnIssue(issue.id, content, p);
|
|
33
|
+
}
|
|
34
|
+
content = "";
|
|
35
|
+
await update();
|
|
36
|
+
};
|
|
37
|
+
const close = async () => {
|
|
38
|
+
state = "loading";
|
|
39
|
+
await FeedbackController.close(issue.id);
|
|
40
|
+
content = "";
|
|
41
|
+
await update();
|
|
42
|
+
};
|
|
43
|
+
const reOpen = async () => {
|
|
44
|
+
state = "loading";
|
|
45
|
+
await FeedbackController.reOpen(issue.id);
|
|
46
|
+
content = "";
|
|
47
|
+
await update();
|
|
48
|
+
};
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<div class="mb-4 grid gap-4">
|
|
52
|
+
{#if state === 'loading'}
|
|
53
|
+
<Loading class="h-12"></Loading>
|
|
54
|
+
<Loading class="h-12"></Loading>
|
|
55
|
+
<Loading class="h-12"></Loading>
|
|
56
|
+
{:else}
|
|
57
|
+
{#each issue?.items ?? [] as item}
|
|
58
|
+
<div class="chat {item.who ? 'chat-start' : 'chat-end'}">
|
|
59
|
+
<div class="avatar chat-image">
|
|
60
|
+
<div class="w-10 rounded-full">
|
|
61
|
+
<div class="h-10 w-10 {item.who ? 'bg-secondary' : 'bg-primary'}"></div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="chat-header">
|
|
65
|
+
{item.who ?? 'Support'}
|
|
66
|
+
<time class="text-xs opacity-50"
|
|
67
|
+
>{new Date(item.createdAt).toLocaleDateString()} - {new Date(
|
|
68
|
+
item.createdAt,
|
|
69
|
+
).toLocaleTimeString()}</time
|
|
70
|
+
>
|
|
71
|
+
</div>
|
|
72
|
+
<div class="chat-bubble">{@html item.bodyHTML}</div>
|
|
73
|
+
<!-- <div class="chat-footer opacity-50">Delivered</div> -->
|
|
74
|
+
</div>
|
|
75
|
+
{/each}
|
|
76
|
+
|
|
77
|
+
{#if issueNumber}
|
|
78
|
+
<button on:click={update} class="divider"></button>
|
|
79
|
+
{/if}
|
|
80
|
+
|
|
81
|
+
{#if issue?.state === 'CLOSED'}
|
|
82
|
+
<div class="flex justify-end">
|
|
83
|
+
<Button on:click={reOpen} class="btn-neutral">Re Ouvrir</Button>
|
|
84
|
+
</div>
|
|
85
|
+
{:else}
|
|
86
|
+
{#if issueNumber === null}
|
|
87
|
+
<Field cell={kitCellBuildor(repo(FilterEntity), 'title')} bind:value={title} />
|
|
88
|
+
{/if}
|
|
89
|
+
<Textarea bind:value={content}></Textarea>
|
|
90
|
+
<div class="flex justify-between">
|
|
91
|
+
{#if issueNumber}
|
|
92
|
+
<Button on:click={close} tabIndex={-1} class="btn-outline btn-error"
|
|
93
|
+
>Clore le feedback</Button
|
|
94
|
+
>
|
|
95
|
+
{:else}
|
|
96
|
+
<div></div>
|
|
97
|
+
{/if}
|
|
98
|
+
<Button on:click={send} disabled={content?.length < 3}>Envoyer</Button>
|
|
99
|
+
</div>
|
|
100
|
+
{/if}
|
|
101
|
+
{/if}
|
|
102
|
+
</div>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
|
2
|
+
declare const __propDef: {
|
|
3
|
+
props: {
|
|
4
|
+
dialogId: number;
|
|
5
|
+
milestoneId: string;
|
|
6
|
+
issueNumber: number | null;
|
|
7
|
+
};
|
|
8
|
+
events: {
|
|
9
|
+
[evt: string]: CustomEvent<any>;
|
|
10
|
+
};
|
|
11
|
+
slots: {};
|
|
12
|
+
exports?: {} | undefined;
|
|
13
|
+
bindings?: string | undefined;
|
|
14
|
+
};
|
|
15
|
+
export type DialogIssueProps = typeof __propDef.props;
|
|
16
|
+
export type DialogIssueEvents = typeof __propDef.events;
|
|
17
|
+
export type DialogIssueSlots = typeof __propDef.slots;
|
|
18
|
+
export default class DialogIssue extends SvelteComponent<DialogIssueProps, DialogIssueEvents, DialogIssueSlots> {
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<script>import { onMount } from "svelte";
|
|
2
|
+
import { FeedbackController } from "..";
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
dialog,
|
|
6
|
+
Icon,
|
|
7
|
+
LibIcon_Add,
|
|
8
|
+
LibIcon_Check,
|
|
9
|
+
LibIcon_Search,
|
|
10
|
+
Loading
|
|
11
|
+
} from "../..";
|
|
12
|
+
import DialogIssue from "./DialogIssue.svelte";
|
|
13
|
+
export let dialogId;
|
|
14
|
+
const rmvWarning = dialogId;
|
|
15
|
+
export let milestoneNumber;
|
|
16
|
+
export let milestoneId;
|
|
17
|
+
let state = "loading";
|
|
18
|
+
let issueState = "OPEN";
|
|
19
|
+
let issues = [];
|
|
20
|
+
const update = async (_issueState) => {
|
|
21
|
+
issueState = _issueState;
|
|
22
|
+
state = "loading";
|
|
23
|
+
issues = await FeedbackController.getIssues(milestoneNumber, issueState);
|
|
24
|
+
state = "done";
|
|
25
|
+
};
|
|
26
|
+
onMount(async () => {
|
|
27
|
+
await update(issueState);
|
|
28
|
+
});
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<div class="mb-4 grid gap-4">
|
|
32
|
+
<div class="flex justify-between">
|
|
33
|
+
<div>
|
|
34
|
+
<Button
|
|
35
|
+
class={issueState === 'OPEN' ? 'btn-primary' : 'btn-ghost'}
|
|
36
|
+
on:click={() => update('OPEN')}>En cours</Button
|
|
37
|
+
>
|
|
38
|
+
<Button
|
|
39
|
+
class={issueState === 'CLOSED' ? 'btn-primary' : 'btn-ghost'}
|
|
40
|
+
on:click={() => update('CLOSED')}>Clos</Button
|
|
41
|
+
>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<Button
|
|
45
|
+
on:click={async () => {
|
|
46
|
+
await dialog.show({
|
|
47
|
+
component: DialogIssue,
|
|
48
|
+
classes: { root: 'overflow-auto w-5/6 h-5/6' },
|
|
49
|
+
props: { issueNumber: null, milestoneId },
|
|
50
|
+
detail: {
|
|
51
|
+
caption: 'Nouveau Feedback',
|
|
52
|
+
icon: { data: LibIcon_Search },
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
await update(issueState)
|
|
56
|
+
}}
|
|
57
|
+
>
|
|
58
|
+
<Icon data={LibIcon_Add}></Icon>
|
|
59
|
+
</Button>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
{#each issues as issue}
|
|
63
|
+
<Button
|
|
64
|
+
on:click={async () => {
|
|
65
|
+
await dialog.show({
|
|
66
|
+
component: DialogIssue,
|
|
67
|
+
classes: { root: 'overflow-auto w-5/6 h-5/6' },
|
|
68
|
+
props: { issueNumber: issue.number, milestoneId },
|
|
69
|
+
detail: {
|
|
70
|
+
caption: issue.titleHTML,
|
|
71
|
+
icon: { data: issue.state === 'OPEN' ? LibIcon_Search : LibIcon_Check },
|
|
72
|
+
},
|
|
73
|
+
})
|
|
74
|
+
await update(issueState)
|
|
75
|
+
}}
|
|
76
|
+
class="btn-neutral"
|
|
77
|
+
>
|
|
78
|
+
<div class="w-full text-left">
|
|
79
|
+
{@html issue.titleHTML}
|
|
80
|
+
</div>
|
|
81
|
+
</Button>
|
|
82
|
+
{:else}
|
|
83
|
+
{#if state === 'loading'}
|
|
84
|
+
<Loading class="h-12"></Loading>
|
|
85
|
+
<Loading class="h-12"></Loading>
|
|
86
|
+
<Loading class="h-12"></Loading>
|
|
87
|
+
{:else}
|
|
88
|
+
<p>Nothing here</p>
|
|
89
|
+
{/if}
|
|
90
|
+
{/each}
|
|
91
|
+
</div>
|