gitflow-analytics 3.8.0__py3-none-any.whl → 3.9.2__py3-none-any.whl
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.
- gitflow_analytics/_version.py +1 -1
- gitflow_analytics/cli_wizards/install_wizard.py +389 -4
- {gitflow_analytics-3.8.0.dist-info → gitflow_analytics-3.9.2.dist-info}/METADATA +120 -19
- {gitflow_analytics-3.8.0.dist-info → gitflow_analytics-3.9.2.dist-info}/RECORD +8 -8
- {gitflow_analytics-3.8.0.dist-info → gitflow_analytics-3.9.2.dist-info}/WHEEL +0 -0
- {gitflow_analytics-3.8.0.dist-info → gitflow_analytics-3.9.2.dist-info}/entry_points.txt +0 -0
- {gitflow_analytics-3.8.0.dist-info → gitflow_analytics-3.9.2.dist-info}/licenses/LICENSE +0 -0
- {gitflow_analytics-3.8.0.dist-info → gitflow_analytics-3.9.2.dist-info}/top_level.txt +0 -0
gitflow_analytics/_version.py
CHANGED
|
@@ -33,7 +33,7 @@ class InstallWizard:
|
|
|
33
33
|
PROFILES = {
|
|
34
34
|
"1": {
|
|
35
35
|
"name": "Standard",
|
|
36
|
-
"description": "GitHub + JIRA + AI (Full featured)",
|
|
36
|
+
"description": "GitHub + PM Tools (JIRA/Linear/ClickUp/GitHub Issues) + AI (Full featured)",
|
|
37
37
|
"github": True,
|
|
38
38
|
"repositories": "manual",
|
|
39
39
|
"jira": True,
|
|
@@ -176,12 +176,55 @@ class InstallWizard:
|
|
|
176
176
|
# Custom mode - ask user
|
|
177
177
|
return False
|
|
178
178
|
|
|
179
|
-
# Step 3:
|
|
179
|
+
# Step 3: PM Platform Setup (conditional based on profile)
|
|
180
180
|
if self.profile["jira"]:
|
|
181
|
-
|
|
181
|
+
# Profile includes PM tools - let user select which ones
|
|
182
|
+
selected_platforms = self._select_pm_platforms()
|
|
183
|
+
|
|
184
|
+
# Setup each selected platform
|
|
185
|
+
if "jira" in selected_platforms:
|
|
186
|
+
self._setup_jira()
|
|
187
|
+
if "linear" in selected_platforms:
|
|
188
|
+
linear_config = self._setup_linear()
|
|
189
|
+
if linear_config:
|
|
190
|
+
if "pm" not in self.config_data:
|
|
191
|
+
self.config_data["pm"] = {}
|
|
192
|
+
self.config_data["pm"]["linear"] = linear_config
|
|
193
|
+
if "clickup" in selected_platforms:
|
|
194
|
+
clickup_config = self._setup_clickup()
|
|
195
|
+
if clickup_config:
|
|
196
|
+
if "pm" not in self.config_data:
|
|
197
|
+
self.config_data["pm"] = {}
|
|
198
|
+
self.config_data["pm"]["clickup"] = clickup_config
|
|
199
|
+
# GitHub Issues uses github.token automatically - no separate setup needed
|
|
200
|
+
|
|
201
|
+
# Store selected platforms for analysis configuration
|
|
202
|
+
self._selected_pm_platforms = selected_platforms
|
|
182
203
|
elif self.profile["jira"] is None:
|
|
183
204
|
# Custom mode - ask user
|
|
184
|
-
self.
|
|
205
|
+
selected_platforms = self._select_pm_platforms()
|
|
206
|
+
|
|
207
|
+
# Setup each selected platform
|
|
208
|
+
if "jira" in selected_platforms:
|
|
209
|
+
self._setup_jira()
|
|
210
|
+
if "linear" in selected_platforms:
|
|
211
|
+
linear_config = self._setup_linear()
|
|
212
|
+
if linear_config:
|
|
213
|
+
if "pm" not in self.config_data:
|
|
214
|
+
self.config_data["pm"] = {}
|
|
215
|
+
self.config_data["pm"]["linear"] = linear_config
|
|
216
|
+
if "clickup" in selected_platforms:
|
|
217
|
+
clickup_config = self._setup_clickup()
|
|
218
|
+
if clickup_config:
|
|
219
|
+
if "pm" not in self.config_data:
|
|
220
|
+
self.config_data["pm"] = {}
|
|
221
|
+
self.config_data["pm"]["clickup"] = clickup_config
|
|
222
|
+
|
|
223
|
+
# Store selected platforms for analysis configuration
|
|
224
|
+
self._selected_pm_platforms = selected_platforms
|
|
225
|
+
else:
|
|
226
|
+
# Profile excludes PM tools
|
|
227
|
+
self._selected_pm_platforms = []
|
|
185
228
|
|
|
186
229
|
# Step 4: OpenRouter/ChatGPT Setup (conditional based on profile)
|
|
187
230
|
if self.profile["ai"]:
|
|
@@ -282,6 +325,64 @@ class InstallWizard:
|
|
|
282
325
|
|
|
283
326
|
return False
|
|
284
327
|
|
|
328
|
+
def _select_pm_platforms(self) -> list:
|
|
329
|
+
"""Let user select which PM platforms to configure.
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
List of selected platform names (e.g., ['jira', 'linear'])
|
|
333
|
+
"""
|
|
334
|
+
click.echo("\n📋 Project Management Platform Selection")
|
|
335
|
+
click.echo("-" * 50)
|
|
336
|
+
click.echo("Select which PM platforms you want to configure:\n")
|
|
337
|
+
click.echo(" 1. JIRA (Atlassian)")
|
|
338
|
+
click.echo(" 2. Linear (linear.app)")
|
|
339
|
+
click.echo(" 3. ClickUp (clickup.com)")
|
|
340
|
+
click.echo(" 4. GitHub Issues (Auto-configured with your GitHub token)")
|
|
341
|
+
click.echo()
|
|
342
|
+
click.echo("Enter numbers separated by spaces or commas (e.g., '1 2 4' or '1,2,4')")
|
|
343
|
+
click.echo("Press Enter to skip all PM platform setup")
|
|
344
|
+
click.echo()
|
|
345
|
+
|
|
346
|
+
selection = click.prompt(
|
|
347
|
+
"Select platforms",
|
|
348
|
+
type=str,
|
|
349
|
+
default="",
|
|
350
|
+
show_default=False,
|
|
351
|
+
).strip()
|
|
352
|
+
|
|
353
|
+
if not selection:
|
|
354
|
+
click.echo("⏭️ Skipping all PM platform setup")
|
|
355
|
+
return []
|
|
356
|
+
|
|
357
|
+
# Parse selection (handle both space and comma separated)
|
|
358
|
+
selection = selection.replace(",", " ")
|
|
359
|
+
choices = selection.split()
|
|
360
|
+
|
|
361
|
+
platforms = []
|
|
362
|
+
platform_map = {"1": "jira", "2": "linear", "3": "clickup", "4": "github"}
|
|
363
|
+
|
|
364
|
+
for choice in choices:
|
|
365
|
+
if choice in platform_map:
|
|
366
|
+
platforms.append(platform_map[choice])
|
|
367
|
+
|
|
368
|
+
if not platforms:
|
|
369
|
+
click.echo(
|
|
370
|
+
"⚠️ No valid platforms selected, defaulting to JIRA for backward compatibility"
|
|
371
|
+
)
|
|
372
|
+
return ["jira"]
|
|
373
|
+
|
|
374
|
+
# Display selected platforms
|
|
375
|
+
platform_names = {
|
|
376
|
+
"jira": "JIRA",
|
|
377
|
+
"linear": "Linear",
|
|
378
|
+
"clickup": "ClickUp",
|
|
379
|
+
"github": "GitHub Issues",
|
|
380
|
+
}
|
|
381
|
+
selected_names = [platform_names[p] for p in platforms]
|
|
382
|
+
click.echo(f"\n✅ Selected platforms: {', '.join(selected_names)}\n")
|
|
383
|
+
|
|
384
|
+
return platforms
|
|
385
|
+
|
|
285
386
|
def _setup_repositories(self) -> bool:
|
|
286
387
|
"""Setup repository configuration.
|
|
287
388
|
|
|
@@ -558,6 +659,271 @@ class InstallWizard:
|
|
|
558
659
|
|
|
559
660
|
click.echo("⏭️ Skipping JIRA setup")
|
|
560
661
|
|
|
662
|
+
def _setup_linear(self) -> Optional[dict]:
|
|
663
|
+
"""Setup Linear integration (optional).
|
|
664
|
+
|
|
665
|
+
Returns:
|
|
666
|
+
Linear configuration dict if successful, None otherwise
|
|
667
|
+
"""
|
|
668
|
+
click.echo("\n📋 Linear Setup")
|
|
669
|
+
click.echo("-" * 50)
|
|
670
|
+
click.echo("\nLinear Configuration:")
|
|
671
|
+
click.echo("You'll need:")
|
|
672
|
+
click.echo(" • Linear API key from: https://linear.app/settings/api")
|
|
673
|
+
click.echo(" • Optional: Team IDs to filter issues")
|
|
674
|
+
click.echo()
|
|
675
|
+
|
|
676
|
+
max_retries = 3
|
|
677
|
+
for attempt in range(max_retries):
|
|
678
|
+
if attempt > 0:
|
|
679
|
+
# Add exponential backoff to prevent rate limiting
|
|
680
|
+
delay = 2 ** (attempt - 1) # 1, 2, 4 seconds
|
|
681
|
+
click.echo(f"⏳ Waiting {delay} seconds before retry...")
|
|
682
|
+
time.sleep(delay)
|
|
683
|
+
|
|
684
|
+
api_key = self._get_password("Linear API key: ", "Linear API key").strip()
|
|
685
|
+
|
|
686
|
+
if not api_key:
|
|
687
|
+
click.echo("❌ API key cannot be empty")
|
|
688
|
+
continue
|
|
689
|
+
|
|
690
|
+
# Validate Linear credentials
|
|
691
|
+
if not self.skip_validation:
|
|
692
|
+
click.echo("🔍 Validating Linear API key...")
|
|
693
|
+
if self._validate_linear(api_key):
|
|
694
|
+
click.echo("✅ Linear API key validated!")
|
|
695
|
+
|
|
696
|
+
# Optional: Team IDs
|
|
697
|
+
team_ids = click.prompt(
|
|
698
|
+
"Team IDs (comma-separated, press Enter to skip)",
|
|
699
|
+
type=str,
|
|
700
|
+
default="",
|
|
701
|
+
show_default=False,
|
|
702
|
+
).strip()
|
|
703
|
+
|
|
704
|
+
# Store configuration
|
|
705
|
+
self.env_data["LINEAR_API_KEY"] = api_key
|
|
706
|
+
linear_config = {
|
|
707
|
+
"api_key": "${LINEAR_API_KEY}",
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
if team_ids:
|
|
711
|
+
team_list = [tid.strip() for tid in team_ids.split(",") if tid.strip()]
|
|
712
|
+
if team_list:
|
|
713
|
+
linear_config["team_ids"] = team_list
|
|
714
|
+
click.echo(f"✅ Configured {len(team_list)} team ID(s)")
|
|
715
|
+
|
|
716
|
+
return linear_config
|
|
717
|
+
else:
|
|
718
|
+
if attempt < max_retries - 1:
|
|
719
|
+
retry = click.confirm("Linear validation failed. Try again?", default=True)
|
|
720
|
+
if not retry:
|
|
721
|
+
click.echo("⏭️ Skipping Linear setup")
|
|
722
|
+
return None
|
|
723
|
+
else:
|
|
724
|
+
click.echo("❌ Maximum retry attempts reached")
|
|
725
|
+
click.echo("⏭️ Skipping Linear setup")
|
|
726
|
+
return None
|
|
727
|
+
else:
|
|
728
|
+
# Skip validation mode
|
|
729
|
+
self.env_data["LINEAR_API_KEY"] = api_key
|
|
730
|
+
return {"api_key": "${LINEAR_API_KEY}"}
|
|
731
|
+
|
|
732
|
+
click.echo("⏭️ Skipping Linear setup")
|
|
733
|
+
return None
|
|
734
|
+
|
|
735
|
+
def _validate_linear(self, api_key: str) -> bool:
|
|
736
|
+
"""Validate Linear API key.
|
|
737
|
+
|
|
738
|
+
Args:
|
|
739
|
+
api_key: Linear API key
|
|
740
|
+
|
|
741
|
+
Returns:
|
|
742
|
+
True if credentials are valid, False otherwise
|
|
743
|
+
"""
|
|
744
|
+
# Suppress requests logging to prevent credential exposure
|
|
745
|
+
urllib3_logger = logging.getLogger("urllib3")
|
|
746
|
+
requests_logger = logging.getLogger("requests")
|
|
747
|
+
original_urllib3 = urllib3_logger.level
|
|
748
|
+
original_requests = requests_logger.level
|
|
749
|
+
|
|
750
|
+
urllib3_logger.setLevel(logging.WARNING)
|
|
751
|
+
requests_logger.setLevel(logging.WARNING)
|
|
752
|
+
|
|
753
|
+
try:
|
|
754
|
+
headers = {
|
|
755
|
+
"Authorization": api_key,
|
|
756
|
+
"Content-Type": "application/json",
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
# Simple GraphQL query to validate authentication
|
|
760
|
+
query = {"query": "{ viewer { name } }"}
|
|
761
|
+
|
|
762
|
+
response = requests.post(
|
|
763
|
+
"https://api.linear.app/graphql",
|
|
764
|
+
headers=headers,
|
|
765
|
+
json=query,
|
|
766
|
+
timeout=10,
|
|
767
|
+
verify=True,
|
|
768
|
+
)
|
|
769
|
+
|
|
770
|
+
if response.status_code == 200:
|
|
771
|
+
data = response.json()
|
|
772
|
+
if "data" in data and "viewer" in data["data"]:
|
|
773
|
+
viewer_name = data["data"]["viewer"].get("name", "Unknown")
|
|
774
|
+
click.echo(f" Authenticated as: {viewer_name}")
|
|
775
|
+
return True
|
|
776
|
+
else:
|
|
777
|
+
click.echo(" Authentication failed: Invalid response")
|
|
778
|
+
return False
|
|
779
|
+
else:
|
|
780
|
+
click.echo(f" Authentication failed (status {response.status_code})")
|
|
781
|
+
return False
|
|
782
|
+
|
|
783
|
+
except requests.exceptions.RequestException as e:
|
|
784
|
+
# Never expose raw exception - could contain credentials
|
|
785
|
+
error_type = type(e).__name__
|
|
786
|
+
click.echo(f" Connection error: {error_type}")
|
|
787
|
+
logger.debug(f"Linear connection error type: {error_type}")
|
|
788
|
+
return False
|
|
789
|
+
except Exception as e:
|
|
790
|
+
click.echo(" Linear validation failed")
|
|
791
|
+
logger.error(f"Linear validation error type: {type(e).__name__}")
|
|
792
|
+
return False
|
|
793
|
+
finally:
|
|
794
|
+
# Always restore logging levels
|
|
795
|
+
urllib3_logger.setLevel(original_urllib3)
|
|
796
|
+
requests_logger.setLevel(original_requests)
|
|
797
|
+
|
|
798
|
+
def _setup_clickup(self) -> Optional[dict]:
|
|
799
|
+
"""Setup ClickUp integration (optional).
|
|
800
|
+
|
|
801
|
+
Returns:
|
|
802
|
+
ClickUp configuration dict if successful, None otherwise
|
|
803
|
+
"""
|
|
804
|
+
click.echo("\n📋 ClickUp Setup")
|
|
805
|
+
click.echo("-" * 50)
|
|
806
|
+
click.echo("\nClickUp Configuration:")
|
|
807
|
+
click.echo("You'll need:")
|
|
808
|
+
click.echo(" • ClickUp API token from: https://app.clickup.com/settings/apps")
|
|
809
|
+
click.echo(" • Workspace URL (e.g., https://app.clickup.com/12345/v/)")
|
|
810
|
+
click.echo()
|
|
811
|
+
|
|
812
|
+
max_retries = 3
|
|
813
|
+
for attempt in range(max_retries):
|
|
814
|
+
if attempt > 0:
|
|
815
|
+
# Add exponential backoff to prevent rate limiting
|
|
816
|
+
delay = 2 ** (attempt - 1) # 1, 2, 4 seconds
|
|
817
|
+
click.echo(f"⏳ Waiting {delay} seconds before retry...")
|
|
818
|
+
time.sleep(delay)
|
|
819
|
+
|
|
820
|
+
api_token = self._get_password("ClickUp API token: ", "ClickUp API token").strip()
|
|
821
|
+
workspace_url = click.prompt("ClickUp workspace URL", type=str).strip()
|
|
822
|
+
|
|
823
|
+
if not all([api_token, workspace_url]):
|
|
824
|
+
click.echo("❌ All ClickUp fields are required")
|
|
825
|
+
continue
|
|
826
|
+
|
|
827
|
+
# Normalize workspace_url
|
|
828
|
+
workspace_url = workspace_url.rstrip("/")
|
|
829
|
+
|
|
830
|
+
# Validate ClickUp credentials
|
|
831
|
+
if not self.skip_validation:
|
|
832
|
+
click.echo("🔍 Validating ClickUp credentials...")
|
|
833
|
+
if self._validate_clickup(api_token):
|
|
834
|
+
click.echo("✅ ClickUp credentials validated!")
|
|
835
|
+
|
|
836
|
+
# Store configuration
|
|
837
|
+
self.env_data["CLICKUP_API_TOKEN"] = api_token
|
|
838
|
+
self.env_data["CLICKUP_WORKSPACE_URL"] = workspace_url
|
|
839
|
+
|
|
840
|
+
clickup_config = {
|
|
841
|
+
"api_token": "${CLICKUP_API_TOKEN}",
|
|
842
|
+
"workspace_url": "${CLICKUP_WORKSPACE_URL}",
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
return clickup_config
|
|
846
|
+
else:
|
|
847
|
+
if attempt < max_retries - 1:
|
|
848
|
+
retry = click.confirm("ClickUp validation failed. Try again?", default=True)
|
|
849
|
+
if not retry:
|
|
850
|
+
click.echo("⏭️ Skipping ClickUp setup")
|
|
851
|
+
return None
|
|
852
|
+
else:
|
|
853
|
+
click.echo("❌ Maximum retry attempts reached")
|
|
854
|
+
click.echo("⏭️ Skipping ClickUp setup")
|
|
855
|
+
return None
|
|
856
|
+
else:
|
|
857
|
+
# Skip validation mode
|
|
858
|
+
self.env_data["CLICKUP_API_TOKEN"] = api_token
|
|
859
|
+
self.env_data["CLICKUP_WORKSPACE_URL"] = workspace_url
|
|
860
|
+
return {
|
|
861
|
+
"api_token": "${CLICKUP_API_TOKEN}",
|
|
862
|
+
"workspace_url": "${CLICKUP_WORKSPACE_URL}",
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
click.echo("⏭️ Skipping ClickUp setup")
|
|
866
|
+
return None
|
|
867
|
+
|
|
868
|
+
def _validate_clickup(self, api_token: str) -> bool:
|
|
869
|
+
"""Validate ClickUp API token.
|
|
870
|
+
|
|
871
|
+
Args:
|
|
872
|
+
api_token: ClickUp API token
|
|
873
|
+
|
|
874
|
+
Returns:
|
|
875
|
+
True if credentials are valid, False otherwise
|
|
876
|
+
"""
|
|
877
|
+
# Suppress requests logging to prevent credential exposure
|
|
878
|
+
urllib3_logger = logging.getLogger("urllib3")
|
|
879
|
+
requests_logger = logging.getLogger("requests")
|
|
880
|
+
original_urllib3 = urllib3_logger.level
|
|
881
|
+
original_requests = requests_logger.level
|
|
882
|
+
|
|
883
|
+
urllib3_logger.setLevel(logging.WARNING)
|
|
884
|
+
requests_logger.setLevel(logging.WARNING)
|
|
885
|
+
|
|
886
|
+
try:
|
|
887
|
+
headers = {
|
|
888
|
+
"Authorization": api_token,
|
|
889
|
+
"Content-Type": "application/json",
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
response = requests.get(
|
|
893
|
+
"https://api.clickup.com/api/v2/user",
|
|
894
|
+
headers=headers,
|
|
895
|
+
timeout=10,
|
|
896
|
+
verify=True,
|
|
897
|
+
)
|
|
898
|
+
|
|
899
|
+
if response.status_code == 200:
|
|
900
|
+
user_info = response.json()
|
|
901
|
+
if "user" in user_info:
|
|
902
|
+
username = user_info["user"].get("username", "Unknown")
|
|
903
|
+
click.echo(f" Authenticated as: {username}")
|
|
904
|
+
return True
|
|
905
|
+
else:
|
|
906
|
+
click.echo(" Authentication failed: Invalid response")
|
|
907
|
+
return False
|
|
908
|
+
else:
|
|
909
|
+
click.echo(f" Authentication failed (status {response.status_code})")
|
|
910
|
+
return False
|
|
911
|
+
|
|
912
|
+
except requests.exceptions.RequestException as e:
|
|
913
|
+
# Never expose raw exception - could contain credentials
|
|
914
|
+
error_type = type(e).__name__
|
|
915
|
+
click.echo(f" Connection error: {error_type}")
|
|
916
|
+
logger.debug(f"ClickUp connection error type: {error_type}")
|
|
917
|
+
return False
|
|
918
|
+
except Exception as e:
|
|
919
|
+
click.echo(" ClickUp validation failed")
|
|
920
|
+
logger.error(f"ClickUp validation error type: {type(e).__name__}")
|
|
921
|
+
return False
|
|
922
|
+
finally:
|
|
923
|
+
# Always restore logging levels
|
|
924
|
+
urllib3_logger.setLevel(original_urllib3)
|
|
925
|
+
requests_logger.setLevel(original_requests)
|
|
926
|
+
|
|
561
927
|
def _validate_jira(self, base_url: str, username: str, api_token: str) -> bool:
|
|
562
928
|
"""Validate JIRA credentials.
|
|
563
929
|
|
|
@@ -946,6 +1312,25 @@ class InstallWizard:
|
|
|
946
1312
|
click.echo(" gitflow-analytics aliases -c config.yaml --apply")
|
|
947
1313
|
click.echo(" This will analyze your repos and generate aliases automatically.\n")
|
|
948
1314
|
|
|
1315
|
+
# Configure ticket platforms based on selected PM tools
|
|
1316
|
+
if hasattr(self, "_selected_pm_platforms") and self._selected_pm_platforms:
|
|
1317
|
+
ticket_platforms = []
|
|
1318
|
+
|
|
1319
|
+
# Add platforms in order of setup
|
|
1320
|
+
if "jira" in self._selected_pm_platforms:
|
|
1321
|
+
ticket_platforms.append("jira")
|
|
1322
|
+
if "linear" in self._selected_pm_platforms:
|
|
1323
|
+
ticket_platforms.append("linear")
|
|
1324
|
+
if "clickup" in self._selected_pm_platforms:
|
|
1325
|
+
ticket_platforms.append("clickup")
|
|
1326
|
+
if "github" in self._selected_pm_platforms or "github" in self.config_data:
|
|
1327
|
+
# GitHub Issues auto-configured with GitHub token
|
|
1328
|
+
ticket_platforms.append("github")
|
|
1329
|
+
|
|
1330
|
+
if ticket_platforms:
|
|
1331
|
+
self.config_data["analysis"]["ticket_platforms"] = ticket_platforms
|
|
1332
|
+
click.echo(f"✅ Configured ticket platforms: {', '.join(ticket_platforms)}\n")
|
|
1333
|
+
|
|
949
1334
|
def _clear_sensitive_data(self) -> None:
|
|
950
1335
|
"""Clear sensitive data from memory after use."""
|
|
951
1336
|
sensitive_keys = ["TOKEN", "KEY", "PASSWORD", "SECRET"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitflow-analytics
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.9.2
|
|
4
4
|
Summary: Analyze Git repositories for developer productivity insights
|
|
5
5
|
Author-email: Bob Matyas <bobmatnyc@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -338,13 +338,13 @@ Here's a complete example showing `.env` file and corresponding YAML configurati
|
|
|
338
338
|
GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx
|
|
339
339
|
GITHUB_ORG=your-organization
|
|
340
340
|
|
|
341
|
-
#
|
|
341
|
+
# PM Platform Configuration
|
|
342
342
|
JIRA_ACCESS_USER=developer@company.com
|
|
343
343
|
JIRA_ACCESS_TOKEN=ATATT3xxxxxxxxxxx
|
|
344
|
+
LINEAR_API_KEY=lin_api_xxxxxxxxxxxx
|
|
345
|
+
CLICKUP_API_TOKEN=pk_xxxxxxxxxxxx
|
|
344
346
|
|
|
345
|
-
#
|
|
346
|
-
# CLICKUP_TOKEN=pk_xxxxxxxxxxxx
|
|
347
|
-
# LINEAR_TOKEN=lin_api_xxxxxxxxxxxx
|
|
347
|
+
# Note: GitHub Issues uses GITHUB_TOKEN automatically
|
|
348
348
|
```
|
|
349
349
|
|
|
350
350
|
### `config.yaml` file
|
|
@@ -356,12 +356,22 @@ github:
|
|
|
356
356
|
token: "${GITHUB_TOKEN}"
|
|
357
357
|
organization: "${GITHUB_ORG}"
|
|
358
358
|
|
|
359
|
-
#
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
359
|
+
# Multi-platform PM integration
|
|
360
|
+
pm:
|
|
361
|
+
jira:
|
|
362
|
+
access_user: "${JIRA_ACCESS_USER}"
|
|
363
|
+
access_token: "${JIRA_ACCESS_TOKEN}"
|
|
364
|
+
base_url: "https://company.atlassian.net"
|
|
364
365
|
|
|
366
|
+
linear:
|
|
367
|
+
api_key: "${LINEAR_API_KEY}"
|
|
368
|
+
team_ids: ["team_123abc"] # Optional: filter by specific teams
|
|
369
|
+
|
|
370
|
+
clickup:
|
|
371
|
+
api_token: "${CLICKUP_API_TOKEN}"
|
|
372
|
+
workspace_url: "https://app.clickup.com/12345/v/"
|
|
373
|
+
|
|
374
|
+
# JIRA story point integration (optional)
|
|
365
375
|
jira_integration:
|
|
366
376
|
enabled: true
|
|
367
377
|
fetch_story_points: true
|
|
@@ -371,9 +381,12 @@ jira_integration:
|
|
|
371
381
|
|
|
372
382
|
# Analysis configuration
|
|
373
383
|
analysis:
|
|
374
|
-
#
|
|
384
|
+
# Track tickets from all configured platforms
|
|
375
385
|
ticket_platforms:
|
|
376
386
|
- jira
|
|
387
|
+
- linear
|
|
388
|
+
- clickup
|
|
389
|
+
- github # GitHub Issues (uses GITHUB_TOKEN)
|
|
377
390
|
|
|
378
391
|
# Exclude bot commits and boilerplate files
|
|
379
392
|
exclude:
|
|
@@ -666,24 +679,97 @@ story_point_patterns:
|
|
|
666
679
|
|
|
667
680
|
## Ticket Platform Support
|
|
668
681
|
|
|
669
|
-
Automatically detects and tracks tickets from:
|
|
682
|
+
Automatically detects and tracks tickets from multiple PM platforms:
|
|
670
683
|
- **JIRA**: `PROJ-123`
|
|
671
|
-
- **GitHub**: `#123`, `GH-123`
|
|
684
|
+
- **GitHub Issues**: `#123`, `GH-123`
|
|
672
685
|
- **ClickUp**: `CU-abc123`
|
|
673
686
|
- **Linear**: `ENG-123`
|
|
674
687
|
|
|
675
|
-
###
|
|
688
|
+
### Multi-Platform PM Integration
|
|
676
689
|
|
|
677
|
-
GitFlow Analytics can
|
|
690
|
+
GitFlow Analytics supports multiple project management platforms simultaneously. You can configure one or more platforms based on your team's workflow:
|
|
678
691
|
|
|
679
692
|
```yaml
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
693
|
+
# Configure which platforms to track
|
|
694
|
+
analysis:
|
|
695
|
+
ticket_platforms:
|
|
696
|
+
- jira
|
|
697
|
+
- linear
|
|
698
|
+
- clickup
|
|
699
|
+
- github # GitHub Issues
|
|
700
|
+
|
|
701
|
+
# Platform-specific configuration
|
|
702
|
+
pm:
|
|
703
|
+
jira:
|
|
704
|
+
access_user: "${JIRA_ACCESS_USER}"
|
|
705
|
+
access_token: "${JIRA_ACCESS_TOKEN}"
|
|
706
|
+
base_url: "https://your-company.atlassian.net"
|
|
707
|
+
|
|
708
|
+
linear:
|
|
709
|
+
api_key: "${LINEAR_API_KEY}"
|
|
710
|
+
team_ids: # Optional: filter by team
|
|
711
|
+
- "team_123abc"
|
|
712
|
+
|
|
713
|
+
clickup:
|
|
714
|
+
api_token: "${CLICKUP_API_TOKEN}"
|
|
715
|
+
workspace_url: "https://app.clickup.com/12345/v/"
|
|
716
|
+
|
|
717
|
+
# GitHub Issues uses existing GitHub token automatically
|
|
718
|
+
github:
|
|
719
|
+
token: "${GITHUB_TOKEN}"
|
|
720
|
+
```
|
|
684
721
|
|
|
722
|
+
### Platform Setup Guides
|
|
723
|
+
|
|
724
|
+
#### JIRA Setup
|
|
725
|
+
1. **Get API Token**: Go to [Atlassian API Tokens](https://id.atlassian.com/manage-profile/security/api-tokens)
|
|
726
|
+
2. **Required Permissions**: Read access to projects and issues
|
|
727
|
+
3. **Configuration**:
|
|
728
|
+
```yaml
|
|
729
|
+
pm:
|
|
730
|
+
jira:
|
|
731
|
+
access_user: "${JIRA_ACCESS_USER}" # Your Atlassian email
|
|
732
|
+
access_token: "${JIRA_ACCESS_TOKEN}"
|
|
733
|
+
base_url: "https://your-company.atlassian.net"
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
#### Linear Setup
|
|
737
|
+
1. **Get API Key**: Go to [Linear Settings → API](https://linear.app/settings/api)
|
|
738
|
+
2. **Required Permissions**: Read access to issues
|
|
739
|
+
3. **Configuration**:
|
|
740
|
+
```yaml
|
|
741
|
+
pm:
|
|
742
|
+
linear:
|
|
743
|
+
api_key: "${LINEAR_API_KEY}"
|
|
744
|
+
team_ids: ["team_123abc"] # Optional: specify team IDs
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
#### ClickUp Setup
|
|
748
|
+
1. **Get API Token**: Go to [ClickUp Settings → Apps](https://app.clickup.com/settings/apps)
|
|
749
|
+
2. **Get Workspace URL**: Copy from browser when viewing your workspace
|
|
750
|
+
3. **Configuration**:
|
|
751
|
+
```yaml
|
|
752
|
+
pm:
|
|
753
|
+
clickup:
|
|
754
|
+
api_token: "${CLICKUP_API_TOKEN}"
|
|
755
|
+
workspace_url: "https://app.clickup.com/12345/v/"
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
#### GitHub Issues Setup
|
|
759
|
+
GitHub Issues is automatically enabled when GitHub integration is configured. No additional setup required:
|
|
760
|
+
```yaml
|
|
761
|
+
github:
|
|
762
|
+
token: "${GITHUB_TOKEN}" # Same token for repo access and issues
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
### JIRA Story Point Integration
|
|
766
|
+
|
|
767
|
+
GitFlow Analytics can fetch story points directly from JIRA tickets:
|
|
768
|
+
|
|
769
|
+
```yaml
|
|
685
770
|
jira_integration:
|
|
686
771
|
enabled: true
|
|
772
|
+
fetch_story_points: true
|
|
687
773
|
story_point_fields:
|
|
688
774
|
- "Story point estimate" # Your custom field name
|
|
689
775
|
- "customfield_10016" # Or use field ID
|
|
@@ -694,6 +780,21 @@ To discover your JIRA story point fields:
|
|
|
694
780
|
gitflow-analytics discover-storypoint-fields -c config.yaml
|
|
695
781
|
```
|
|
696
782
|
|
|
783
|
+
### Environment Variables for Credentials
|
|
784
|
+
|
|
785
|
+
Store credentials securely in a `.env` file:
|
|
786
|
+
|
|
787
|
+
```bash
|
|
788
|
+
# .env file (keep this secure and don't commit to git!)
|
|
789
|
+
GITHUB_TOKEN=ghp_your_token_here
|
|
790
|
+
|
|
791
|
+
# PM Platform Credentials
|
|
792
|
+
JIRA_ACCESS_USER=your.email@company.com
|
|
793
|
+
JIRA_ACCESS_TOKEN=ATATT3xxxxxxxxxxx
|
|
794
|
+
LINEAR_API_KEY=lin_api_xxxxxxxxxxxx
|
|
795
|
+
CLICKUP_API_TOKEN=pk_xxxxxxxxxxxx
|
|
796
|
+
```
|
|
797
|
+
|
|
697
798
|
## Caching
|
|
698
799
|
|
|
699
800
|
The tool uses SQLite for intelligent caching:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
gitflow_analytics/__init__.py,sha256=W3Jaey5wuT1nBPehVLTIRkVIyBa5jgYOlBKc_UFfh-4,773
|
|
2
|
-
gitflow_analytics/_version.py,sha256=
|
|
2
|
+
gitflow_analytics/_version.py,sha256=1vJ2cOJm4JBq3ASHO5cRbx3_lFVH3QIh1Caj_cSjWN4,137
|
|
3
3
|
gitflow_analytics/cli.py,sha256=pYW6V0b6SRa3-NyOmXGQhf5emcKHUHgOVL2PFOAS8LQ,273331
|
|
4
4
|
gitflow_analytics/config.py,sha256=XRuxvzLWyn_ML7mDCcuZ9-YFNAEsnt33vIuWxQQ_jxg,1033
|
|
5
5
|
gitflow_analytics/constants.py,sha256=GXEncUJS9ijOI5KWtQCTANwdqxPfXpw-4lNjhaWTKC4,2488
|
|
@@ -11,7 +11,7 @@ gitflow_analytics/classification/feature_extractor.py,sha256=W82vztPQO8-MFw9Yt17
|
|
|
11
11
|
gitflow_analytics/classification/linguist_analyzer.py,sha256=HjLx9mM7hGXtrvMba6osovHJLAacTx9oDmN6CS5w0bE,17687
|
|
12
12
|
gitflow_analytics/classification/model.py,sha256=2KbmFh9MpyvHMcNHbqwUTAAVLHHu3MiTfFIPyZSGa-8,16356
|
|
13
13
|
gitflow_analytics/cli_wizards/__init__.py,sha256=D73D97cS1hZsB_fCQQaAiWtd_w2Lb8TtcGc9Pn2DIyE,343
|
|
14
|
-
gitflow_analytics/cli_wizards/install_wizard.py,sha256=
|
|
14
|
+
gitflow_analytics/cli_wizards/install_wizard.py,sha256=KcmDD2RiqFrPuXdBhmXCYvftlV_V-_7p82Z6Gu8NgO0,60817
|
|
15
15
|
gitflow_analytics/cli_wizards/run_launcher.py,sha256=J6G_C7IqxPg7_GhAfbV99D1dIIWwb1s_qmHC7Iv2iGI,15038
|
|
16
16
|
gitflow_analytics/config/__init__.py,sha256=KziRIbBJctB5LOLcKLzELWA1rXwjS6-C2_DeM_hT9rM,1133
|
|
17
17
|
gitflow_analytics/config/aliases.py,sha256=z9F0X6qbbF544Tw7sHlOoBj5mpRSddMkCpoKLzvVzDU,10960
|
|
@@ -120,9 +120,9 @@ gitflow_analytics/training/model_loader.py,sha256=xGZLSopGxDhC--2XN6ytRgi2CyjOKY
|
|
|
120
120
|
gitflow_analytics/training/pipeline.py,sha256=PQegTk_-OsPexVyRDfiy-3Df-7pcs25C4vPASr-HT9E,19951
|
|
121
121
|
gitflow_analytics/ui/__init__.py,sha256=UBhYhZMvwlSrCuGWjkIdoP2zNbiQxOHOli-I8mqIZUE,441
|
|
122
122
|
gitflow_analytics/ui/progress_display.py,sha256=3xJnCOSs1DRVAfS-rTu37EsLfWDFW5-mbv-bPS9NMm4,59182
|
|
123
|
-
gitflow_analytics-3.
|
|
124
|
-
gitflow_analytics-3.
|
|
125
|
-
gitflow_analytics-3.
|
|
126
|
-
gitflow_analytics-3.
|
|
127
|
-
gitflow_analytics-3.
|
|
128
|
-
gitflow_analytics-3.
|
|
123
|
+
gitflow_analytics-3.9.2.dist-info/licenses/LICENSE,sha256=xwvSwY1GYXpRpmbnFvvnbmMwpobnrdN9T821sGvjOY0,1066
|
|
124
|
+
gitflow_analytics-3.9.2.dist-info/METADATA,sha256=zFtrIA8qcqvHtfmDMA9wqTLf2RQbhweU02RzzqqeerY,36830
|
|
125
|
+
gitflow_analytics-3.9.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
126
|
+
gitflow_analytics-3.9.2.dist-info/entry_points.txt,sha256=ZOsX0GLsnMysp5FWPOfP_qyoS7WJ8IgcaDFDxWBYl1g,98
|
|
127
|
+
gitflow_analytics-3.9.2.dist-info/top_level.txt,sha256=CQyxZXjKvpSB1kgqqtuE0PCRqfRsXZJL8JrYpJKtkrk,18
|
|
128
|
+
gitflow_analytics-3.9.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|