Compare commits
No commits in common. "8ced9107f5fea77668453397d92c0330dbcbb74c" and "c3e219c2b5746888a56c754941174c23794a683b" have entirely different histories.
8ced9107f5
...
c3e219c2b5
10 changed files with 663 additions and 878 deletions
|
|
@ -7,8 +7,5 @@ charset = utf-8
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
[src/logic/*]
|
|
||||||
indent_size = 4
|
|
||||||
|
|
||||||
[*.md]
|
[*.md]
|
||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = false
|
||||||
|
|
|
||||||
32
.eslintrc
32
.eslintrc
|
|
@ -2,35 +2,5 @@
|
||||||
"extends": [
|
"extends": [
|
||||||
"oclif",
|
"oclif",
|
||||||
"oclif-typescript"
|
"oclif-typescript"
|
||||||
],
|
]
|
||||||
"rules": {
|
|
||||||
"quotes": "off",
|
|
||||||
"indent": "off",
|
|
||||||
"semi": "off",
|
|
||||||
"unicorn/prefer-node-protocol": "off",
|
|
||||||
"unicorn/prefer-ternary": "off",
|
|
||||||
"dot-notation": "off",
|
|
||||||
"padding-line-between-statements": "off",
|
|
||||||
"camelcase": "off",
|
|
||||||
"unicorn/no-console-spaces": "off",
|
|
||||||
"new-cap": "off",
|
|
||||||
"no-await-in-loop": "off",
|
|
||||||
"unicorn/no-for-loop": "off",
|
|
||||||
"comma-dangle": "off",
|
|
||||||
"node/no-missing-import": "off",
|
|
||||||
"no-else-return": "off",
|
|
||||||
"quote-props": "off",
|
|
||||||
"no-inner-declarations": "off",
|
|
||||||
"no-process-exit": "off",
|
|
||||||
"unicorn/no-process-exit": "off",
|
|
||||||
"eol-last": "off",
|
|
||||||
"no-prototype-builtins": "off",
|
|
||||||
"padded-blocks": "off",
|
|
||||||
"unicorn/prefer-number-properties": "off",
|
|
||||||
"operator-assignment": "off",
|
|
||||||
"no-negated-condition": "off",
|
|
||||||
"unicorn/catch-error-name": "off",
|
|
||||||
"@typescript-eslint/ban-ts-comment": "off",
|
|
||||||
"unicorn/prefer-includes": "off"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
359
README.md
359
README.md
|
|
@ -1,148 +1,26 @@
|
||||||
Redmine Time Manager
|
oclif-hello-world
|
||||||
====================
|
=================
|
||||||
|
|
||||||
Cli tool for redmine time managment
|
oclif example Hello World CLI
|
||||||
|
|
||||||
Main idea:
|
[](https://oclif.io)
|
||||||
|
[](https://npmjs.org/package/oclif-hello-world)
|
||||||
* You prepare configuration for transformation your csv for redmine time entries api format only once
|
[](https://circleci.com/gh/oclif/hello-world/tree/main)
|
||||||
* Regularly call this script for transfering your time entries and saving it in redmine
|
[](https://npmjs.org/package/oclif-hello-world)
|
||||||
|
[](https://github.com/oclif/hello-world/blob/main/package.json)
|
||||||
Examples
|
|
||||||
========
|
|
||||||
|
|
||||||
Transformation from Microsoft Excel or LibreOffice Calc or GoogleDocs Spreadsheets:
|
|
||||||
|
|
||||||
Your configuration (`config.json`) may be as:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"csv": {
|
|
||||||
"delimiter": "\t",
|
|
||||||
"columns": true,
|
|
||||||
"encoding": "utf8",
|
|
||||||
"quote": false
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"columns": {
|
|
||||||
"issue": "Issue",
|
|
||||||
"activity": "Action",
|
|
||||||
"comment": "Description",
|
|
||||||
"time": "Time"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"time_type": "time",
|
|
||||||
"date_source": "argument",
|
|
||||||
"date_format": "yyyy-MM-dd",
|
|
||||||
"issue_regexp": "\\d+",
|
|
||||||
"redmine": {
|
|
||||||
"url": "http://token@redmine.example.org",
|
|
||||||
"user_id": 123,
|
|
||||||
"default_issue": 456,
|
|
||||||
"activities": {
|
|
||||||
"Code": 1,
|
|
||||||
"Code Review": 2,
|
|
||||||
"Test": 3
|
|
||||||
},
|
|
||||||
"default_activity_code": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Next for GNU/Linux with Xorg, you can select table, tap CTRL+C for coping in system clipboard, and call command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
xsel -o | redmine-time-manager save --config=config.json --date=2022-02-01 --rewrite
|
|
||||||
```
|
|
||||||
|
|
||||||
Or for other system you can save into csv file (for example `my-daily-time.csv`), and call command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
redmine-time-manager save --config=config.json --from-file=my-daily-time.csv --date=2022-02-01
|
|
||||||
```
|
|
||||||
|
|
||||||
Transformation from [Hamster Time Tracker](https://github.com/projecthamster/hamster):
|
|
||||||
|
|
||||||
Your configuration (`config.json`) may be as:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"csv": {
|
|
||||||
"delimiter": "\t",
|
|
||||||
"columns": true,
|
|
||||||
"encoding": "utf8",
|
|
||||||
"quote": false
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"columns": {
|
|
||||||
"issue": "занятие",
|
|
||||||
"activity": "метки",
|
|
||||||
"comment": "описание",
|
|
||||||
"time": "длительность в минутах",
|
|
||||||
"date": "время начала",
|
|
||||||
"category": "категория"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"time_type": "minutes",
|
|
||||||
"date_source": "column",
|
|
||||||
"date_format": "yyyy-MM-dd HH:mm",
|
|
||||||
"issue_regexp": "\\d+",
|
|
||||||
"query": "select * from ? where category = 'Work'",
|
|
||||||
"redmine": {
|
|
||||||
"url": "http://token@redmine.example.org",
|
|
||||||
"user_id": 123,
|
|
||||||
"default_issue": 456,
|
|
||||||
"activities": {
|
|
||||||
"Code": 1,
|
|
||||||
"Code Review": 2,
|
|
||||||
"Test": 3
|
|
||||||
},
|
|
||||||
"default_activity_code": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Hamster Time Tracker supported export reports to tsv format (csv by tab delimiters), and you can call command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
redmine-time-manager save --config=config.json --from-file=my-daily-time.tsv
|
|
||||||
```
|
|
||||||
|
|
||||||
Description of config params
|
|
||||||
============================
|
|
||||||
|
|
||||||
* `csv` - for this section see https://csv.js.org/parse/options/
|
|
||||||
* `rules.columns` - mapping rules for column names, must defined aliases for time, issue, activity, date, comment
|
|
||||||
* `time_type` - time format, may be "hours"/"minutes"/"time"
|
|
||||||
* `date_source` - source of date, may be "column"/"argument"
|
|
||||||
* `issue_regexp` - regexp of issue number, mostly - `"\\d+"`
|
|
||||||
* `date_format` - format of date, for example `yyyy-MM-dd`
|
|
||||||
* `redmine` - params for redmine
|
|
||||||
* `url`
|
|
||||||
* `user_id` - your user id
|
|
||||||
* `default_issue` - default issue number
|
|
||||||
* `activities` - mapping rules of activity_id aliases in your Redmine instance
|
|
||||||
|
|
||||||
Additional:
|
|
||||||
|
|
||||||
* `query` - filter of date like sql syntax \
|
|
||||||
for example: `select * from ? where category = 'Work'`
|
|
||||||
|
|
||||||
# TOC
|
|
||||||
|
|
||||||
<!-- toc -->
|
<!-- toc -->
|
||||||
* [TOC](#toc)
|
|
||||||
* [Usage](#usage)
|
* [Usage](#usage)
|
||||||
* [Commands](#commands)
|
* [Commands](#commands)
|
||||||
<!-- tocstop -->
|
<!-- tocstop -->
|
||||||
# Usage
|
# Usage
|
||||||
<!-- usage -->
|
<!-- usage -->
|
||||||
```sh-session
|
```sh-session
|
||||||
$ npm install -g redmine-time-manager
|
$ npm install -g redmine-time-manager-2
|
||||||
$ redmine-time-manager COMMAND
|
$ redmine-time-manager COMMAND
|
||||||
running command...
|
running command...
|
||||||
$ redmine-time-manager (--version)
|
$ redmine-time-manager (--version)
|
||||||
redmine-time-manager/0.2.3 linux-x64 node-v21.0.0
|
redmine-time-manager-2/0.0.0 linux-x64 node-v16.13.1
|
||||||
$ redmine-time-manager --help [COMMAND]
|
$ redmine-time-manager --help [COMMAND]
|
||||||
USAGE
|
USAGE
|
||||||
$ redmine-time-manager COMMAND
|
$ redmine-time-manager COMMAND
|
||||||
|
|
@ -151,8 +29,56 @@ USAGE
|
||||||
<!-- usagestop -->
|
<!-- usagestop -->
|
||||||
# Commands
|
# Commands
|
||||||
<!-- commands -->
|
<!-- commands -->
|
||||||
|
* [`redmine-time-manager hello PERSON`](#redmine-time-manager-hello-person)
|
||||||
|
* [`redmine-time-manager hello world`](#redmine-time-manager-hello-world)
|
||||||
* [`redmine-time-manager help [COMMAND]`](#redmine-time-manager-help-command)
|
* [`redmine-time-manager help [COMMAND]`](#redmine-time-manager-help-command)
|
||||||
* [`redmine-time-manager save`](#redmine-time-manager-save)
|
* [`redmine-time-manager plugins`](#redmine-time-manager-plugins)
|
||||||
|
* [`redmine-time-manager plugins:inspect PLUGIN...`](#redmine-time-manager-pluginsinspect-plugin)
|
||||||
|
* [`redmine-time-manager plugins:install PLUGIN...`](#redmine-time-manager-pluginsinstall-plugin)
|
||||||
|
* [`redmine-time-manager plugins:link PLUGIN`](#redmine-time-manager-pluginslink-plugin)
|
||||||
|
* [`redmine-time-manager plugins:uninstall PLUGIN...`](#redmine-time-manager-pluginsuninstall-plugin)
|
||||||
|
* [`redmine-time-manager plugins update`](#redmine-time-manager-plugins-update)
|
||||||
|
* [`redmine-time-manager save [FILE]`](#redmine-time-manager-save-file)
|
||||||
|
|
||||||
|
## `redmine-time-manager hello PERSON`
|
||||||
|
|
||||||
|
Say hello
|
||||||
|
|
||||||
|
```
|
||||||
|
USAGE
|
||||||
|
$ redmine-time-manager hello [PERSON] -f <value>
|
||||||
|
|
||||||
|
ARGUMENTS
|
||||||
|
PERSON Person to say hello to
|
||||||
|
|
||||||
|
FLAGS
|
||||||
|
-f, --from=<value> (required) Whom is saying hello
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Say hello
|
||||||
|
|
||||||
|
EXAMPLES
|
||||||
|
$ oex hello friend --from oclif
|
||||||
|
hello friend from oclif! (./src/commands/hello/index.ts)
|
||||||
|
```
|
||||||
|
|
||||||
|
_See code: [dist/commands/hello/index.ts](https://github.com/pavel-g/redmine-time-manager-cli/blob/v0.0.0/dist/commands/hello/index.ts)_
|
||||||
|
|
||||||
|
## `redmine-time-manager hello world`
|
||||||
|
|
||||||
|
Say hello world
|
||||||
|
|
||||||
|
```
|
||||||
|
USAGE
|
||||||
|
$ redmine-time-manager hello world
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Say hello world
|
||||||
|
|
||||||
|
EXAMPLES
|
||||||
|
$ oex hello world
|
||||||
|
hello world! (./src/commands/hello/world.ts)
|
||||||
|
```
|
||||||
|
|
||||||
## `redmine-time-manager help [COMMAND]`
|
## `redmine-time-manager help [COMMAND]`
|
||||||
|
|
||||||
|
|
@ -174,29 +100,170 @@ DESCRIPTION
|
||||||
|
|
||||||
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v5.1.10/src/commands/help.ts)_
|
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v5.1.10/src/commands/help.ts)_
|
||||||
|
|
||||||
## `redmine-time-manager save`
|
## `redmine-time-manager plugins`
|
||||||
|
|
||||||
Save time entries to redmine. Full documentation in README.
|
List installed plugins.
|
||||||
|
|
||||||
```
|
```
|
||||||
USAGE
|
USAGE
|
||||||
$ redmine-time-manager save -c <value> [--from-file <value>] [--date <value>] [--dry] [--rewrite]
|
$ redmine-time-manager plugins [--core]
|
||||||
|
|
||||||
FLAGS
|
FLAGS
|
||||||
-c, --config=<value> (required) [default: redmine-time-manager-config.json] Json config
|
--core Show core plugins.
|
||||||
--date=<value> Date. Must defined when config.date_source = "argument"
|
|
||||||
--dry For testing calls. Delete and write operations will be disabled.
|
|
||||||
--from-file=<value> Csv file. If undefined, will use stdin
|
|
||||||
--rewrite Redmine data at choosed date will be rewrited.
|
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
Save time entries to redmine. Full documentation in README.
|
List installed plugins.
|
||||||
|
|
||||||
EXAMPLES
|
EXAMPLES
|
||||||
$ redmine-time-manager save --config=config.json --date=2000-01-01 --rewrite --from-file=my-work-time.csv # reading from csv file
|
$ redmine-time-manager plugins
|
||||||
|
|
||||||
$ xsel -o | redmine-time-manager save --config=config.json --date=2000-01-01 --rewrite # reading csv from stdin from Xorg clipboard
|
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [dist/commands/save.ts](https://github.com/pavel-g/redmine-time-manager/blob/v0.2.3/dist/commands/save.ts)_
|
_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v2.0.11/src/commands/plugins/index.ts)_
|
||||||
|
|
||||||
|
## `redmine-time-manager plugins:inspect PLUGIN...`
|
||||||
|
|
||||||
|
Displays installation properties of a plugin.
|
||||||
|
|
||||||
|
```
|
||||||
|
USAGE
|
||||||
|
$ redmine-time-manager plugins:inspect PLUGIN...
|
||||||
|
|
||||||
|
ARGUMENTS
|
||||||
|
PLUGIN [default: .] Plugin to inspect.
|
||||||
|
|
||||||
|
FLAGS
|
||||||
|
-h, --help Show CLI help.
|
||||||
|
-v, --verbose
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Displays installation properties of a plugin.
|
||||||
|
|
||||||
|
EXAMPLES
|
||||||
|
$ redmine-time-manager plugins:inspect myplugin
|
||||||
|
```
|
||||||
|
|
||||||
|
## `redmine-time-manager plugins:install PLUGIN...`
|
||||||
|
|
||||||
|
Installs a plugin into the CLI.
|
||||||
|
|
||||||
|
```
|
||||||
|
USAGE
|
||||||
|
$ redmine-time-manager plugins:install PLUGIN...
|
||||||
|
|
||||||
|
ARGUMENTS
|
||||||
|
PLUGIN Plugin to install.
|
||||||
|
|
||||||
|
FLAGS
|
||||||
|
-f, --force Run yarn install with force flag.
|
||||||
|
-h, --help Show CLI help.
|
||||||
|
-v, --verbose
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Installs a plugin into the CLI.
|
||||||
|
|
||||||
|
Can be installed from npm or a git url.
|
||||||
|
|
||||||
|
Installation of a user-installed plugin will override a core plugin.
|
||||||
|
|
||||||
|
e.g. If you have a core plugin that has a 'hello' command, installing a user-installed plugin with a 'hello' command
|
||||||
|
will override the core plugin implementation. This is useful if a user needs to update core plugin functionality in
|
||||||
|
the CLI without the need to patch and update the whole CLI.
|
||||||
|
|
||||||
|
ALIASES
|
||||||
|
$ redmine-time-manager plugins add
|
||||||
|
|
||||||
|
EXAMPLES
|
||||||
|
$ redmine-time-manager plugins:install myplugin
|
||||||
|
|
||||||
|
$ redmine-time-manager plugins:install https://github.com/someuser/someplugin
|
||||||
|
|
||||||
|
$ redmine-time-manager plugins:install someuser/someplugin
|
||||||
|
```
|
||||||
|
|
||||||
|
## `redmine-time-manager plugins:link PLUGIN`
|
||||||
|
|
||||||
|
Links a plugin into the CLI for development.
|
||||||
|
|
||||||
|
```
|
||||||
|
USAGE
|
||||||
|
$ redmine-time-manager plugins:link PLUGIN
|
||||||
|
|
||||||
|
ARGUMENTS
|
||||||
|
PATH [default: .] path to plugin
|
||||||
|
|
||||||
|
FLAGS
|
||||||
|
-h, --help Show CLI help.
|
||||||
|
-v, --verbose
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Links a plugin into the CLI for development.
|
||||||
|
|
||||||
|
Installation of a linked plugin will override a user-installed or core plugin.
|
||||||
|
|
||||||
|
e.g. If you have a user-installed or core plugin that has a 'hello' command, installing a linked plugin with a 'hello'
|
||||||
|
command will override the user-installed or core plugin implementation. This is useful for development work.
|
||||||
|
|
||||||
|
EXAMPLES
|
||||||
|
$ redmine-time-manager plugins:link myplugin
|
||||||
|
```
|
||||||
|
|
||||||
|
## `redmine-time-manager plugins:uninstall PLUGIN...`
|
||||||
|
|
||||||
|
Removes a plugin from the CLI.
|
||||||
|
|
||||||
|
```
|
||||||
|
USAGE
|
||||||
|
$ redmine-time-manager plugins:uninstall PLUGIN...
|
||||||
|
|
||||||
|
ARGUMENTS
|
||||||
|
PLUGIN plugin to uninstall
|
||||||
|
|
||||||
|
FLAGS
|
||||||
|
-h, --help Show CLI help.
|
||||||
|
-v, --verbose
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Removes a plugin from the CLI.
|
||||||
|
|
||||||
|
ALIASES
|
||||||
|
$ redmine-time-manager plugins unlink
|
||||||
|
$ redmine-time-manager plugins remove
|
||||||
|
```
|
||||||
|
|
||||||
|
## `redmine-time-manager plugins update`
|
||||||
|
|
||||||
|
Update installed plugins.
|
||||||
|
|
||||||
|
```
|
||||||
|
USAGE
|
||||||
|
$ redmine-time-manager plugins update [-h] [-v]
|
||||||
|
|
||||||
|
FLAGS
|
||||||
|
-h, --help Show CLI help.
|
||||||
|
-v, --verbose
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Update installed plugins.
|
||||||
|
```
|
||||||
|
|
||||||
|
## `redmine-time-manager save [FILE]`
|
||||||
|
|
||||||
|
Save time entries to redmine
|
||||||
|
|
||||||
|
```
|
||||||
|
USAGE
|
||||||
|
$ redmine-time-manager save [FILE] [-n <value>] [-f]
|
||||||
|
|
||||||
|
FLAGS
|
||||||
|
-f, --force
|
||||||
|
-n, --name=<value> name to print
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Save time entries to redmine
|
||||||
|
|
||||||
|
EXAMPLES
|
||||||
|
$ redmine-time-manager save
|
||||||
|
```
|
||||||
|
|
||||||
|
_See code: [dist/commands/save.ts](https://github.com/pavel-g/redmine-time-manager-cli/blob/v0.0.0/dist/commands/save.ts)_
|
||||||
<!-- commandsstop -->
|
<!-- commandsstop -->
|
||||||
|
|
|
||||||
10
package.json
10
package.json
|
|
@ -1,15 +1,15 @@
|
||||||
{
|
{
|
||||||
"name": "redmine-time-manager",
|
"name": "redmine-time-manager-2",
|
||||||
"version": "0.2.3",
|
"version": "0.1.0",
|
||||||
"description": "Redmine Time Manager",
|
"description": "Redmine Time Manager",
|
||||||
"author": "Pavel Gnedov @pavel-g",
|
"author": "Pavel Gnedov @pavel-g",
|
||||||
"bin": {
|
"bin": {
|
||||||
"redmine-time-manager": "./bin/run"
|
"redmine-time-manager": "./bin/run"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/pavel-g/redmine-time-manager",
|
"homepage": "https://github.com/pavel-g/redmine-time-manager-cli",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"repository": "pavel-g/redmine-time-manager",
|
"repository": "pavel-g/redmine-time-manager-cli",
|
||||||
"files": [
|
"files": [
|
||||||
"/bin",
|
"/bin",
|
||||||
"/dist",
|
"/dist",
|
||||||
|
|
@ -64,7 +64,7 @@
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0"
|
"node": ">=12.0.0"
|
||||||
},
|
},
|
||||||
"bugs": "https://github.com/pavel-g/redmine-time-manager/issues",
|
"bugs": "https://github.com/pavel-g/redmine-time-manager-cli/issues",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"oclif"
|
"oclif"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ export const defaultConfig: ConfigTypes.Config = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getConfig(force = false): ConfigTypes.Config {
|
export function getConfig(force: boolean = false): ConfigTypes.Config {
|
||||||
if (!force && !Args.args['config']) {
|
if (!force && !Args.args['config']) {
|
||||||
return defaultConfig;
|
return defaultConfig;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,16 @@
|
||||||
import {config} from "./config";
|
import {config} from "./config";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
type TimeEntry = Record<string, any>;
|
export async function loadEntries(date: string): Promise<Record<string, any>> {
|
||||||
type TimeEntriesResponse = {time_entries: TimeEntry[]};
|
|
||||||
|
|
||||||
export async function loadEntries(date: string): Promise<TimeEntry[]> {
|
|
||||||
const url = `${config.redmine.url}/time_entries.json`;
|
const url = `${config.redmine.url}/time_entries.json`;
|
||||||
const params = {
|
const params = {
|
||||||
user_id: config.redmine.user_id,
|
user_id: config.redmine.user_id,
|
||||||
from: date,
|
from: date,
|
||||||
to: date
|
to: date
|
||||||
};
|
};
|
||||||
const resp = await axios.get<TimeEntriesResponse>(url, {params: params});
|
const resp = await axios.get<Record<string, any>>(url, {params: params});
|
||||||
if (!resp || resp.status !== 200 || !resp.data || !resp.data.time_entries) {
|
if (!resp || resp.status !== 200 || !resp.data || !resp.data.time_entries) {
|
||||||
throw new Error('Не удалось загрузить записи за дату');
|
throw new Error('Не удалось загрузить записи за дату');
|
||||||
}
|
}
|
||||||
return resp.data.time_entries.filter((entry) => {
|
return resp.data.time_entries;
|
||||||
return entry.user.id === config.redmine.user_id;
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -5,17 +5,15 @@ import {config} from "./config";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import {loadEntries} from "./open";
|
import {loadEntries} from "./open";
|
||||||
import {ColumnConverter, filterByQuery, getDate, getItemForRedmine, TimeEntryForRedmine} from "./csv";
|
import {ColumnConverter, filterByQuery, getDate, getItemForRedmine, TimeEntryForRedmine} from "./csv";
|
||||||
import {sleep, uniq} from './utils';
|
import {uniq} from "./utils";
|
||||||
import GetStdin from '../utils/get-stdin';
|
|
||||||
|
|
||||||
function readContentFromFile(fileName: string): string {
|
function readContentFromFile(fileName: string): string {
|
||||||
return fs.readFileSync(fileName, {encoding: 'utf8'});
|
return fs.readFileSync(fileName, {encoding: 'utf8'});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readContentFromStdin(): Promise<string> {
|
async function readContentFromStdin(): Promise<string> {
|
||||||
// const GetStdin = await import('get-stdin');
|
const GetStdin = await import('get-stdin');
|
||||||
// const GetStdin = await import('get-stdin');
|
return await GetStdin.default();
|
||||||
return await GetStdin();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readContent(): Promise<string> {
|
async function readContent(): Promise<string> {
|
||||||
|
|
@ -28,47 +26,32 @@ async function readContent(): Promise<string> {
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
function backupEntries(entries: Record<string, any>[]): void {
|
function backupEntries(entries: Record<string, any>): void {
|
||||||
const fileName = `entries-${args['date']}.json`;
|
const fileName = `entries-${args['date']}.json`;
|
||||||
const content = JSON.stringify(entries);
|
const content = JSON.stringify(entries);
|
||||||
fs.writeFileSync(fileName, content, {encoding: "utf8"});
|
fs.writeFileSync(fileName, content, {encoding: "utf8"});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteEntries(entries: Record<string, any>[], userId: any): Promise<void> {
|
async function deleteEntries(entries: Record<string, any>): Promise<void> {
|
||||||
let i: number;
|
await Promise.all(
|
||||||
for (i = 0; i < entries.length; i++) {
|
entries.map(async (entry: Record<string, any>) => {
|
||||||
const entry = entries[i];
|
const url = `${config.redmine.url}/time_entries/${entry.id}.xml`;
|
||||||
if (entry?.user?.id != userId) {
|
if (args['dry']) {
|
||||||
continue;
|
console.log('Delete time entry:', {url, entry});
|
||||||
}
|
} else {
|
||||||
const url = `${config.redmine.url}/time_entries/${entry.id}.xml`;
|
await axios.delete(url);
|
||||||
if (args['dry']) {
|
}
|
||||||
console.log('Delete time entry:', {url, entry});
|
})
|
||||||
} else {
|
);
|
||||||
await axios.delete(url);
|
|
||||||
}
|
|
||||||
await sleep(100);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cleanTimeEntries(items: TimeEntryForRedmine[], userId: any): Promise<void> {
|
async function cleanTimeEntries(items: TimeEntryForRedmine[]): Promise<void> {
|
||||||
const dates = getUniqDates(items);
|
const dates = getUniqDates(items);
|
||||||
for (let i = 0; i < dates.length; i++) {
|
for (let i = 0; i < dates.length; i++) {
|
||||||
const date = dates[i];
|
const date = dates[i];
|
||||||
let entries;
|
const entries = await loadEntries(date);
|
||||||
try {
|
|
||||||
entries = await loadEntries(date);
|
|
||||||
} catch (ex) {
|
|
||||||
console.error(`Ошибка при поиске существующих записей на ${date}`, ex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
backupEntries(entries);
|
backupEntries(entries);
|
||||||
try {
|
await deleteEntries(entries);
|
||||||
await deleteEntries(entries, userId);
|
|
||||||
} catch (ex) {
|
|
||||||
console.error(`Ошибка при удалении записей на ${date}`, ex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,12 +72,7 @@ async function saveItem(item: TimeEntryForRedmine): Promise<boolean> {
|
||||||
if (args['dry']) {
|
if (args['dry']) {
|
||||||
console.log('Save time entry:', {url, data, params: params});
|
console.log('Save time entry:', {url, data, params: params});
|
||||||
} else {
|
} else {
|
||||||
let resp;
|
const resp = await axios.post(url, data, {params: params});
|
||||||
try {
|
|
||||||
resp = await axios.post(url, data, {params: params});
|
|
||||||
} catch (ex) {
|
|
||||||
console.error('Ошибка при сохранении', {url: url, data: data, resp: resp}); // DEBUG
|
|
||||||
}
|
|
||||||
if (!resp || !resp.data) {
|
if (!resp || !resp.data) {
|
||||||
console.error(`Не удалось сохранить в redmine запись `, item);
|
console.error(`Не удалось сохранить в redmine запись `, item);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -113,7 +91,6 @@ async function saveCsv(csvData: TimeEntryForRedmine[]): Promise<void> {
|
||||||
}
|
}
|
||||||
const saveResult = await saveItem(item);
|
const saveResult = await saveItem(item);
|
||||||
if (saveResult) successCount++;
|
if (saveResult) successCount++;
|
||||||
await sleep(100);
|
|
||||||
}
|
}
|
||||||
console.log(`Сохранено записей: ${successCount}`);
|
console.log(`Сохранено записей: ${successCount}`);
|
||||||
}
|
}
|
||||||
|
|
@ -136,20 +113,8 @@ export async function save(): Promise<void> {
|
||||||
})
|
})
|
||||||
.map(item => item.item) as TimeEntryForRedmine[];
|
.map(item => item.item) as TimeEntryForRedmine[];
|
||||||
|
|
||||||
if (args['rewrite']) {
|
if (args['rewrite']) await cleanTimeEntries(items);
|
||||||
console.log('Очистка существующих записей...')
|
|
||||||
const userId = config.redmine.user_id;
|
|
||||||
if (!userId) {
|
|
||||||
console.error('В конфигурационном файле не указан user_id');
|
|
||||||
process.exit(1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await cleanTimeEntries(items, userId);
|
|
||||||
console.log('Очистка существующих записей завершена')
|
|
||||||
}
|
|
||||||
console.log('Сохранение новых записей...');
|
|
||||||
await saveCsv(items);
|
await saveCsv(items);
|
||||||
console.log('Сохранение новых записей завершено');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUniqDates(items: TimeEntryForRedmine[]): string[] {
|
export function getUniqDates(items: TimeEntryForRedmine[]): string[] {
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,34 @@
|
||||||
export function invert(obj: Record<any, any>): Record<any, any> {
|
export function invert(obj: Record<any, any>): Record<any, any> {
|
||||||
const newObj = {}
|
const newObj = {};
|
||||||
let key
|
let key;
|
||||||
for (key in obj) {
|
for (key in obj) {
|
||||||
if (!obj.hasOwnProperty(key)) continue
|
if (!obj.hasOwnProperty(key)) continue;
|
||||||
const value = obj[key]
|
const value = obj[key];
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
newObj[value] = key
|
newObj[value] = key;
|
||||||
}
|
}
|
||||||
return newObj
|
return newObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function uniq<T>(src: T[]): T[] {
|
export function uniq<T>(src: T[]): T[] {
|
||||||
const res = []
|
const res = [];
|
||||||
let i: number
|
let i: number;
|
||||||
for (i = 0; i < src.length; i++) {
|
for (i = 0; i < src.length; i++) {
|
||||||
const value = src[i]
|
const value = src[i];
|
||||||
if (res.indexOf(value) < 0) {
|
if (res.indexOf(value) < 0) {
|
||||||
res.push(value)
|
res.push(value);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return res
|
}
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function assign(target: Record<string, any>, src: Record<string, any>): Record<string, any> {
|
export function assign(target: Record<string, any>, src: Record<string, any>): Record<string, any> {
|
||||||
let key: string
|
let key: string;
|
||||||
for (key in src) {
|
for (key in src) {
|
||||||
if (src.hasOwnProperty(key)) {
|
if (src.hasOwnProperty(key)) {
|
||||||
console.debug('rewrite key:', key, ' with value:', src[key]) // DEBUG
|
console.debug('rewrite key:', key, ' with value:', src[key]); // DEBUG
|
||||||
target[key] = src[key]
|
target[key] = src[key];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return target
|
}
|
||||||
}
|
return target;
|
||||||
|
|
||||||
export async function sleep(timeout: number): Promise<void> {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
setTimeout(resolve, timeout)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
const {stdin} = process;
|
|
||||||
|
|
||||||
export default async function getStdin() {
|
|
||||||
let result = '';
|
|
||||||
|
|
||||||
if (stdin.isTTY) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
stdin.setEncoding('utf8');
|
|
||||||
|
|
||||||
for await (const chunk of stdin) {
|
|
||||||
result += chunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
getStdin.buffer = async () => {
|
|
||||||
const result = [];
|
|
||||||
let length = 0;
|
|
||||||
|
|
||||||
if (stdin.isTTY) {
|
|
||||||
return Buffer.concat([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
for await (const chunk of stdin) {
|
|
||||||
result.push(chunk);
|
|
||||||
length += chunk.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Buffer.concat(result, length);
|
|
||||||
};
|
|
||||||
Loading…
Reference in a new issue