አዎ, እኔ Vilian Iaumbaev ነኝ! በ iOS እና Android ሁለቱም ለሁሉም አዲስ የኮርፖሬሽኖች ማረጋገጫ ስርዓት የተገነባለሁ - ይህ ችግሮች ለማከናወን እና ለመፍጠር በጣም ቀላል ያደርጋል. እኛ ምን ማድረግ ነበር. በእርግጠኝነት ለስላሳ መተግበሪያዎች መውሰድ መውሰድ በፍጥነት አንድ አስደናቂ እና የማይታመን ተግባር ተደርጓል. አንድ ነገር መውሰድ ቀላል ነበር, ከባድ ምሳሌዎች መውሰድ, ወይም ተስማሚ ጥያቄዎች መውሰድ. የእኛን ሂደት ይበልጥ ማረጋገጥ እና ስርዓተ ማድረግ ይፈልጋል - ስለዚህ የቴክኖሎጂ ቡድን ማንኛውንም ጊዜ መውሰድ ጊዜ መውሰድ አለብዎት, እና ምንም አስፈላጊ ጥያቄዎች ከባድ ሊሆን ይችላል. ዝርዝር ለመጀመር, እንደ Google መሣሪያዎች አለብዎት አግኙን እና Jira. የ Crashlytics የ Google Cloud አውታረ መረብ የ Crashlytics የ Google Cloud አውታረ መረብ የእርስዎ ፕሮጀክቶች በ Google አገልግሎቶች ውስጥ የተመሠረተ በኋላ, የ Crashlytics ከ GCP ወደ ውሂብ አቅርቦት በመጠቀም ከዚያ በኋላ, ሁሉንም ክወና ሪፖርት በ BigQuery ቴሌሎች ውስጥ ይመልከቱ. አግኙን ገጽ አግኙን ገጽ ለ iOS እና ለ Android የቁማር ውሂብ መዋቅር ከሁለቱም ተመሳሳይ ናቸው, ከሁለቱም ትንሽ ልዩነት ጋር - ይህም ማለት እኛም ሁለቱም ለማከናወን አንድ ስክሪፕን መጠቀም ይችላሉ. ስለዚህ አሁን በ BigQuery ውስጥ የእርስዎን ክወናዎች አለብዎት ይህም በዚህ ውሂብ ላይ አንዳንድ ሥራ መጠቀም ይችላሉ. እርስዎ ሁሉንም ክወና ውሂብ ማግኘት ይችላሉ እና እርስዎ የሚፈልጉት መንገድ ያካትታል. እኔ Python ቋንቋ ይምረጡ እና በዚህ ምሳሌ ላይ እርስዎ ያውቃል. የመጀመሪያው, እኛ ሁሉንም crashes ውሂብ ለማሳየት ያስፈልጋቸዋል, ነገር ግን አንድ ሚሊዮን በላይ ተጠቃሚዎች ላይ ትልቅ ውሂብ አጠቃቀም ከሆነ ከ Google ገጽ ላይ ሁሉንም ውሂብ preprocessing, አንዳንድ አጠቃቀም ማድረግ. ፕሮግራም ከ BigQuery ከ crash data ያግኙ Python በመጠቀም Query data crash ሁሉንም committers ከ repozitory መውሰድ እና መውሰድ duplicates ያግኙ እያንዳንዱ ውሂብ ወደ repo ፋይሎችን እና እያንዳንዱ አግኝቷል የ Jira ተግባር ለመፍጠር, አንድ ተግባር አሁን አይሆንም ከሆነ, ፋይሎችን ባለቤት ከ BigQuery ውሂብ ለማግኘት አንዳንድ የ SQL ጥቅሞች ያንብቡ BigQuery የእርስዎ የ SQL dialect ይጠቀማል, ይህም መደበኛ SQL ጋር ተመሳሳይ ነው ነገር ግን ውሂብ ልውውጥ ለማግኘት ተጨማሪ ተስማሚነት ይሰጣል. የእኛን መተግበሪያ ለማግኘት, ሙሉ የቁማር ውሂብ ስብስቦች ጋር ይሰራለን, ነገር ግን በአጠቃቀም ቅርጸት. ልዩነት, የእኛን ልዩ የቁማር ውሂብ መልዕክቶች በቁማር ትዕዛዞች ውስጥ የተመሠረተ እና ከዚያም በእያንዳንዱ ስብስቦች ውስጥ ተመሳሳይ ውሂብ ያካትታል - ለምሳሌ, ተጽዕኖዎች ቁጥር, ተጽዕኖ ውሂብ ቁጥር, ስሪት ቅርጸት, እና ተጨማሪ. https://console.cloud.google.com/bigquery https://console.cloud.google.com/bigquery WITH pre as( SELECT issue_id, ARRAY_AGG(DISTINCT issue_title IGNORE NULLS) as issue_titles, ARRAY_AGG(DISTINCT blame_frame.file IGNORE NULLS) as blame_files, ARRAY_AGG(DISTINCT blame_frame.library IGNORE NULLS) as blame_libraries, ARRAY_AGG(DISTINCT blame_frame.symbol IGNORE NULLS) as blame_symbols, COUNT(DISTINCT event_id) as total_events, COUNT(DISTINCT installation_uuid) as total_users, '{"version":"' || application.display_version || '","events":' || COUNT(DISTINCT event_id) || ',"users":' || COUNT(DISTINCT installation_uuid) || '}' AS events_info FROM `YOUR TABLE NAME` WHERE 1=1 AND event_timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 24 HOUR) AND event_timestamp < CURRENT_TIMESTAMP() AND error_type = "FATAL" GROUP BY issue_id, application.display_version ) SELECT issue_id, ARRAY_CONCAT_AGG(issue_titles) as issue_titles, ARRAY_CONCAT_AGG(blame_files) as blame_files, ARRAY_CONCAT_AGG(blame_libraries) as blame_libraries, ARRAY_CONCAT_AGG(blame_symbols) as blame_symbols, SUM(total_events) as total_events, SUM(total_users) as total_users, '[' || STRING_AGG(events_info, ",") || ']' as events_info FROM pre WHERE 1=1 AND issue_id IS NOT NULL AND events_info IS NOT NULL GROUP BY issue_id ORDER BY total_users DESC እንደዚህ, በእያንዳንዱ ልዩ issue_id አንድ መስኮት ያገኛሉ, በተጨማሪም የሚከተሉት አጠቃቀም መስኮቶች: issue_titles — ሁሉንም crash titles ዝርዝር ነው. ይህ አንድ ስዕል ላይ አንድ ስዕል ላይ አንድ ስዕል ላይ ብዙ ልዩ ስዕሎች ሊሆን ይችላል ጊዜዎች ለማረጋገጥ አንድ ስዕል ነው. በ scripting ክፍል, we will select the most frequent one. blame_files — ከባድ stacktrace ፋይሎች ዝርዝር. ይህ ከባድ ኮድ ቤዝ ውስጥ (በ ስርዓቱ ካፒታል ውስጥ ይልቅ) ከባድ አይሆንም. blame_libraries — አንድ መጋቢዎች ዝርዝር, ይህ ደግሞ አንድ መጋቢ ነው, issue_titles ጋር ተመሳሳይ ምክንያት የተገነባው. blame_symbols — የኮድ ስሜቶች (ፋንክሪንግ / ዘዴዎች) ዝርዝር, በዚያ ላይ የኮድ ስሜቶች ተደርጓል. ከሌሎች ገጾች እንደ, ይህ አንድ መለያ ነው. Total_Events — የተመሠረተ ጊዜው ወቅት የኮርፖሬሽኖች አጠቃላይ ቁጥር. total_users — የተወሰነ ተጠቃሚዎች ቁጥር. አንዳንድ ጊዜ ብቻ አንድ የተወሰነ ቡድን ተጠቃሚዎች ብቻ ሊሆን ይችላል. events_info — total_events እና total_users ያካትታሉ የ JSON መሳሪያዎች (ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወደ-ወ [ { "version": "1.0.1", "events": 131, "users": 110 }, { "version": "1.2.1", "events": 489, "users": 426 } ] ከ BigQuery ከ Python በመጠቀም ውሂብ ይሰጣል ለመጀመር ከ BigQuery Python ደንበኞች ካፒታል መጫን ለመጫን በኋላ, አንድ BigQueryExecutor.py ፋይሎች ለመፍጠር - ይህ ሞዱል በ Google Cloud BigQuery ጋር ሁሉንም ግንኙነት ይጠቀማል. PyPI ፎቶዎች import os import json import tempfile from google.oauth2 import service_account from google.cloud import bigquery from collections import Counter class BigQueryExecutor: def __init__(self, credentialsJson: str, bqProjectId: str = ''): temp_file_path='' with tempfile.NamedTemporaryFile(mode='w', delete=False) as temp_file: json.dump(json.loads(credentialsJson), temp_file, indent=4) temp_file_path = temp_file.name credentials = service_account.Credentials.from_service_account_file(temp_file_path) os.remove(temp_file_path) self.client = bigquery.Client(project=bqProjectId, credentials=credentials) የ script መጠቀም ለመጀመር, አንተ ብቻ ሁለት ነገሮች ይፈልጋሉ: የ Google አገልግሎት መለያ JSON ማረጋገጫ ፋይሎች የእርስዎ BigQuery ፕሮጀክት ስም (ወይ) አንድ ጊዜ እነዚህን አግኝቷል, እርስዎ ማረጋገጫ ይችላሉ እና ስክሪት በመጠቀም ጥያቄዎችን ለመጀመር. የ Google አገልግሎት መለያ JSON Credential አንድ መለያ ለመፍጠር, ወደ እና ያግኙን ተጫዋች የ Google Cloud Console BigQuery Data Editor የ Google Cloud Console መለያው ለመፍጠር በኋላ, በመስመር ላይ ያግኙ, ወደ ግምገማዎች, click እና ይምረጡ ይህ service account ለ JSON credentials ፋይል ለመፍጠር እና ማውረድ ይሆናል. “Keys” “Add key” “JSON” አንድ አገልግሎት መለያ JSON በአጠቃላይ እንደዚህ ይሆናል: { "type": "service_account", "project_id": YOUR_PROJECT, "private_key_id": private_key_id, "private_key": GCP_PRIVATE_KEY, "client_email": "email", "client_id": "id", "auth_uri": "auth_uri", "token_uri": "token_uri", "auth_provider_x509_cert_url": "auth_provider_x509_cert_url", "client_x509_cert_url": "url", "universe_domain": "universe_domain" } የ JSON ማረጋገጫዎችን ለሙከራ መተግበሪያዎች መተግበሪያዎችን ለሙከራ መተግበሪያዎች መተግበሪያዎች መተግበሪያዎች መተግበሪያዎች መተግበሪያዎች መተግበሪያዎች መተግበሪያዎች መተግበሪያዎች መተግበሪያዎች መተግበሪያዎች መተግበሪያዎች መተግበሪያዎች መተግበሪያዎች መተግበሪያዎች መተግበሪያዎች መተግበሪያዎች መተግበሪያዎች መተግበሪያዎች መተግበሪያዎች መተግበሪያዎች መተግበሪያዎች — አንድ Secrets Manager ይጠቀማል, በእርስዎ ትዕዛዞች ለመጠበቅ እና ለመቆጣጠር. not recommended for production በተጨማሪም የእርስዎ bqProjectId ከ project_id ገጽ በ JSON ማረጋገጫዎች ውስጥ መውሰድ ይችላሉ. ሞዴሎች የ BigQuery ውሂብ ጋር በይነገጽ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግበሪያ-የተግ እዚህ እንደዚህ ሞዴሎች አንድ ምሳሌ ነው: class BQCrashlyticsVersionsModel: def __init__(self, version: str, events: int, users: int ): self.version = version self.events = events self.users = users class BQCrashlyticsIssueModel: def __init__(self, issue_id: str, issue_title: str, blame_file: str, blame_library: str, blame_symbol: str, total_events: int, total_users: int, versions: list[BQCrashlyticsVersionsModel] ): self.issue_id = issue_id self.issue_title = issue_title self.blame_file = blame_file self.blame_library = blame_library self.blame_symbol = blame_symbol self.total_events = total_events self.total_users = total_users self.versions = versions የኮምፒውተር ተግባር ከዚያም ከ BigQuery ውሂብ ማግኘት ይችላሉ. የሚገኝ BigQueryExecutor ክፍል ውስጥ የሚከተለው ዘዴ ያካትታሉ — ይህ ከላይ ውስጥ የተመሠረተ SQL ጥያቄ ይሰራል የፕሮግራም ልምድ እና የፕሮግራም ልምድ በፕሮግራም ልምድ. የ SQL አጠቃቀም def getCrashlyticsIssues(self, lastHoursCount: int, tableName: str) -> list[BQCrashlyticsIssueModel]: firstEventsInfo = """'[' || STRING_AGG(events_info, ",") || ']' as events_info""" asVersions = """ '{"version":"' || application.display_version || '","events":' || COUNT(DISTINCT event_id) || ',"users":' || COUNT(DISTINCT installation_uuid) || '}' AS events_info """ query = f""" WITH pre as( SELECT issue_id, ARRAY_AGG(DISTINCT issue_title IGNORE NULLS) as issue_titles, ARRAY_AGG(DISTINCT blame_frame.file IGNORE NULLS) as blame_files, ARRAY_AGG(DISTINCT blame_frame.library IGNORE NULLS) as blame_libraries, ARRAY_AGG(DISTINCT blame_frame.symbol IGNORE NULLS) as blame_symbols, COUNT(DISTINCT event_id) as total_events, COUNT(DISTINCT installation_uuid) as total_users, {asVersions} FROM `{tableName}` WHERE 1=1 AND event_timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL {lastHoursCount} HOUR) AND event_timestamp < CURRENT_TIMESTAMP() AND error_type = "FATAL" GROUP BY issue_id, application.display_version ) SELECT issue_id, ARRAY_CONCAT_AGG(issue_titles) as issue_titles, ARRAY_CONCAT_AGG(blame_files) as blame_files, ARRAY_CONCAT_AGG(blame_libraries) as blame_libraries, ARRAY_CONCAT_AGG(blame_symbols) as blame_symbols, SUM(total_events) as total_events, SUM(total_users) as total_users, {firstEventsInfo} FROM pre WHERE 1=1 AND issue_id IS NOT NULL AND events_info IS NOT NULL GROUP BY issue_id ORDER BY total_users DESC """ bqRows = self.client.query(query).result() rows: list[BQCrashlyticsIssueModel] = [] def mergeArray(array: list[str]) -> str: if not array: return '' counter = Counter(array) most_common = counter.most_common(1) return most_common[0][0] if most_common else '' for row in bqRows: issueModel = BQCrashlyticsIssueModel( issue_id=row.issue_id, issue_title=mergeArray(row.issue_titles), blame_file=mergeArray(row.blame_files), blame_library=mergeArray(row.blame_libraries), blame_symbol=mergeArray(row.blame_symbols), total_events=row.total_events, total_users=row.total_users, versions=[BQCrashlyticsVersionsModel(version=jj['version'], events=jj['events'], users=jj['users']) for jj in json.loads(row.events_info)] ) rows.append(issueModel) return rows አሁን የእኛን SQL ትዕዛዞችን ከ Python ወደ BigQuery መተግበሪያ ይችላሉ. እዚህ የ query እንዴት ለመጀመር እና ውጤቶች ጋር ሥራ እንዴት ሙሉ ምሳሌ ነው: executor = BigQueryExecutor(credentialsJson=_jsonToken, bqProjectId=_bqProjectId) allCrashes = executor.getCrashlyticsIssues(lastHoursCount=24, tableName="tableNAME_IOS_REALTIME") for i in allCrashes: print(i.issue_title) [libswift_Concurrency.dylib] swift::swift_Concurrency_fatalError(unsigned int, char const*, ...) [SwiftUI] static DisplayList.ViewUpdater.Platform.updateClipShapesAsync(layer:oldState:newState:) Foundation [SwiftUI] __swift_memcpy112_8 [libswift_Concurrency.dylib] swift::AsyncTask::waitFuture(swift::AsyncTask*, swift::AsyncContext*, void (swift::AsyncContext* swift_async_context) swiftasynccall*, swift::AsyncContext*, swift::OpaqueValue*) [libobjc.A.dylib] objc_opt_respondsToSelector [VectorKit] void md::COverlayRenderLayer::layoutRibbon<md::Ribbons::PolylineOverlayRibbonDescriptor>(std::__1::unique_ptr<md::PolylineOverlayLayer<md::Ribbons::PolylineOverlayRibbonDescriptor>, std::__1::default_delete<md::PolylineOverlayLayer<md::Ribbons::PolylineOverlayRibbonDescriptor> > > const&, ggl::CommandBuffer*, md::PolylineOverlayLayoutContext&, unsigned int, unsigned long long, bool, bool, float) [libswiftCore.dylib] _swift_release_dealloc [libobjc.A.dylib] objc_msgSend 🎉 አሁን ከ BigQuery ከ ተለዋዋጭ ውሂብ መውሰድ ይችላሉ, እኛ ቀጣይ ደረጃ ላይ ሊሆን ይችላሉ - ከፍተኛ 5 በጣም ተለዋጭ ተለዋዋጭ ውሂብ መውሰድ እና በእነርሱ ለ Jira ተግባሮች ራስ-ሰር ለመፍጠር. የኮምፒውተር አጠቃቀም እና አጠቃቀም መተግበሪያዎች ለወደፊቱ ችግሮች ለማስተካከል በፊት, በመጀመሪያ እያንዳንዱ መተግበሪያ ለወደፊቱ የሚፈልጉ ማንኛውንም አግኝተዋል ያስፈልጋል. ይህ ለማስተካከል, የእኛን መተግበሪያው ከሁሉም አግኝተዋል አግኝተዋል አግኝተናል. የእኛን GitHub ይጠቀማል ጊዜ, አንዳንድ ልዩ ዝርዝሮች ማወቅ አለባቸው: አንዳንድ መተግበሪያዎች በ commits ላይ በርካታ ኢሜይል አድራሻዎችን ሊጠቀሙ ይችላሉ, ስለዚህ የሚፈልጉትን ሁኔታዎች በይነገጽ ይጠቀማሉ. GitHub አብዛኛውን ጊዜ noreply ኢሜይልዎችን ይጠቀማል (እም. username@users.noreply.github.com), ስለዚህ እነዚህ ሁኔታዎች በተመለከተ ይጠቀማል. በዚህ ደረጃ ላይ ዋና መተግበሪያው የ Git መጻቢዎች ዝርዝርን, የእነርሱ ስም እና ኢሜይል ጋር መውሰድ እና በዚህ ኮምፒውተሮች ይጠቀማል: መግቢያ መግቢያ መግቢያ መግቢያ መግቢያ መግቢያ መግቢያ መግቢያ import re class GitUserModel: def __init__(self, nicknames: set[str], emails: set[str], gitLogins: set[str] ): self.nicknames = nicknames self.emails = emails self.gitLogins = gitLogins def returnPossibleNicknames(text: str) -> set[str]: res = [findEmail(text), loginFromEmail(text), findGitNoreplyLogin(text)] return set(list(filter(None, res))) def findEmail(text: str) -> str: e = re.match(r"(([A-Za-z0-9+\.\_\-]*@[A-Za-z0-9+]*\.[A-Za-z0-9+]*))", text) if e: return e.group(1) def loginFromEmail(text: str) -> str: e = re.match(r"(([A-Za-z0-9+\.\_\-]*))@[A-Za-z0-9+]*\.[A-Za-z0-9+]*", text) if e: return e.group(1) def findGitNoreplyLogin(text: str) -> str: gu = re.match(r"\d+\+(([A-Za-z0-9+\.\_\-]*))@users\.noreply\.github\.com", text) if gu: return gu.group(1) else: gu = re.match(r"(([A-Za-z0-9+\.\_\-]*))@users\.noreply\.github\.com", text) if gu: return gu.group(1) class GitBlamer: def getAllRepoUsersMap(self, projectRootPath: str) -> list[GitUserModel]: users: list[GitUserModel] = [] allGitLog = os.popen("cd {}; git log | grep '^Author' | sort | uniq -c".format(projectRootPath)).read() for line in allGitLog.split('\n'): user = self._createUserFromBlameLine(line) if user: users.append(user) self._enrichUsersNicknames(users=users) users = self._mergeSameUsers(users) users = sorted(users, key=lambda x: list(x.emails)[0] if x.emails else list(x.gitLogins)[0] if x.gitLogins else "") return users def _createUserFromBlameLine(self, line): m = re.match(r".* Author: (.*) <(.*)>", line) user = GitUserModel(nicknames=set(), emails=set(), gitLogins=set()) if m: val=set() if m.group(1): val.add(m.group(1)) if m.group(2): val.add(m.group(2)) user.nicknames = val else: return return user def _enrichUsersNicknames(self, users: list[GitUserModel]): for user in users: possibleNicknames = set() for nick in user.nicknames: possibleNicknames = possibleNicknames.union(returnPossibleNicknames(text=nick)) e = findEmail(text=nick) if e: user.emails.add(e) gu = findGitNoreplyLogin(text=nick) if gu: user.gitLogins.add(gu) user.nicknames = user.nicknames.union(possibleNicknames) def _mergeSameUsers(self, users: list[GitUserModel]): for i in range(0, len(users)): if i >= len(users): break for j in range(i+1, len(users)): if j >= len(users): break setLoweredJNicknames=set([u.lower() for u in users[j].nicknames]) for k in range(0, j): if k >= j: break setLoweredKNicknames=set([u.lower() for u in users[k].nicknames]) isSameNickname=len(setLoweredKNicknames.intersection(setLoweredJNicknames)) > 0 if isSameNickname: users[j].gitLogins = users[j].gitLogins.union(users[k].gitLogins) users[j].emails = users[j].emails.union(users[k].emails) users.pop(k) break return users በዚህ ኮድ ውስጥ, በእርግጥ ተመሳሳይ ሰው ሊሆን ይችላል የተለያዩ ትክክለኛነትዎች ተኳሃኝ ይሆናል - ለምሳሌ, user@gmail.com እና user@users.noreply.github.com. በተጨማሪም የእነርሱ ስም እና GitHub ተጠቃሚ ስም (እነዚህ ላይ ይገኛል) ለስላሳነት አግኝቷል. ከታች ያለውን ስክሪት ጋር, ይህ ሂደት መጀመር ይችላሉ እና የፕላስቲክ ውስጥ ሁሉንም committers የተቀየደው, deduplicated ዝርዝር ማግኘት ይችላሉ: projectRootPath="/IOS_project_path" blamer = GitBlamer() allUsers = blamer.getAllRepoUsersMap(projectRootPath=projectRootPath) for user in allUsers: print(", ".join(user.nicknames)) እያንዳንዱ ውሂብ ወደ Repository እና File Owner ፋይሎችን ያካትታል በዚህ ጊዜ, እኛም የእኛን መቁረጫዎች እና የእኛን መቁረጫዎች የሚጠቀሙ ተጠቃሚዎች ስለ ዝርዝር መረጃ አግኝቷል. ይህ የእኛን የተወሰኑ መቁረጫን በእያንዳንዱ ተጠቃሚ ጋር ያካትታሉ እና በቀላሉ ተስማሚ Jira ተግባር ለመፍጠር ይቻላል. የ iOS እና የ Android workflows መተግበሪያዎችን ለመተግበሪያ በፊት የ iOS እና የ Android workflows መተግበሪያዎች የተለያዩ የኮምፒውተር ቅርጸቶች ይጠቀማሉ, እና የኮምፒውተር ቅርጸቶች ጋር የኮምፒውተር ቅርጸት መተግበሪያዎች መተግበሪያዎችን ይጠቀማሉ. በዚህን ደህንነት ለማግኘት, የኮምፒውተር ቅርጸት እና የኮምፒውተር ቅርጸት መተግበሪያዎችን ይጠቀማሉ. class AbstractFileToIssueMapper: def isPathBelongsToIssue(self, file: str, filePath: str, issue: BQCrashlyticsIssueModel) -> bool: raise Exception('Not implemented method AbstractFileToIssueMapper') class AndroidFileToIssueMapper(AbstractFileToIssueMapper): def __init__(self): self.libraryName = 'inDrive' def isPathBelongsToIssue(self, file: str, filePath: str, issue: BQCrashlyticsIssueModel) -> bool: if file != issue.blame_file or not issue.blame_symbol.startswith(self.libraryName): return False fileNameNoExtension = file.split('.')[0] fileNameIndexInSymbol = issue.blame_symbol.find(fileNameNoExtension) if fileNameIndexInSymbol < 0: return False relativeFilePathFromSymbol = issue.blame_symbol[0:fileNameIndexInSymbol].replace('.', '/') relativeFilePathFromSymbol = relativeFilePathFromSymbol + file return filePath.endswith(relativeFilePathFromSymbol) class IosFileToIssueMapper(AbstractFileToIssueMapper): def __init__(self): self.indriveLibraryName = 'inDrive' self.indriveFolderName = 'inDrive' self.modulesFolderName = 'Modules' def isPathBelongsToIssue(self, file: str, filePath: str, issue: BQCrashlyticsIssueModel) -> bool: if file != issue.blame_file: return False isMatchFolder = False if issue.blame_library == self.indriveLibraryName: isMatchFolder = filePath.startswith('{}/'.format(self.indriveFolderName)) else: isMatchFolder = filePath.startswith('{}/{}/'.format(self.modulesFolderName, issue.blame_library)) return isMatchFolder የተወሰነ መተግበሪያ በፕሮጀክትዎ ላይ የተለያየ ሊሆን ይችላል, ነገር ግን ይህ ክፍል ዋናው ትዕዛዞች አንድ የተወሰነ ፋይሎች ውስጥ አንድ የተወሰነ ክወና ሊሆን ይችላል. አንድ ጊዜ ይህ ትክክለኛነት ተስማሚ ነው, እኛ ፋይሎች ወደ ጥያቄዎች ማተም እና ተስማሚ ፋይሎች አጠቃቀም ሊሆን ይችላል. import subprocess class MappedIssueFileModel: def __init__(self, fileGitLink: str, filePath: str, issue: BQCrashlyticsIssueModel, fileOwner: GitUserModel ): self.fileGitLink = fileGitLink self.filePath = filePath self.issue = issue self.fileOwner = fileOwner class BigQueryCrashesFilesMapper: def getBlameOut(self, filePath: str, projectRootPath: str) -> list[str]: dangerousChars = re.compile(r'[;|&\r\n]|\.\.') if dangerousChars.search(filePath) or dangerousChars.search(projectRootPath): return None if not subprocess.check_output(['git', 'ls-files', filePath], cwd=projectRootPath, text=True): return None blameProc = subprocess.Popen(['git', 'blame', filePath, '-cwe'], cwd=projectRootPath, stdout=subprocess.PIPE, text=True) blameRegex=r'<[a-zA-Z0-9\+\.\_\-]*@[a-zA-Z0-9\+\.\_\-]*>' grepProc = subprocess.Popen(['grep', '-o', blameRegex], stdin=blameProc.stdout, stdout=subprocess.PIPE, text=True) blameProc.stdout.close() sortProc = subprocess.Popen(['sort'], stdin=grepProc.stdout, stdout=subprocess.PIPE, text=True) grepProc.stdout.close() uniqProc = subprocess.Popen(['uniq', '-c'], stdin=sortProc.stdout, stdout=subprocess.PIPE, text=True) sortProc.stdout.close() finalProc = subprocess.Popen(['sort', '-bgr'], stdin=uniqProc.stdout, stdout=subprocess.PIPE, text=True) uniqProc.stdout.close() blameOut, _ = finalProc.communicate() blameArray=list(filter(len, blameOut.split('\n'))) return blameArray def findFileOwner(self, filePath: str, gitFileOwners: list[GitUserModel], projectRootPath: str) -> GitUserModel: blameArray = self.getBlameOut(filePath=filePath, projectRootPath=projectRootPath) if not blameArray: return foundAuthor = None for blameI in range(0, len(blameArray)): author = re.match(r".*\d+ <(.*)>", blameArray[blameI]) if author: possibleNicknames = returnPossibleNicknames(text=author.group(1)) for gitFileOwner in gitFileOwners: if len(gitFileOwner.nicknames.intersection(possibleNicknames)) > 0: foundAuthor = gitFileOwner break if foundAuthor: break return foundAuthor def mapBQResultsWithFiles(self, fileToIssueMapper: AbstractFileToIssueMapper, issues: list[BQCrashlyticsIssueModel], gitFileOwners: list[GitUserModel], projectRootPath: str ) -> list[MappedIssueFileModel]: mappedArray: list[MappedIssueFileModel] = [] githubMainBranch = "https://github.com/inDriver/UDF/blob/master" for root, dirs, files in os.walk(projectRootPath): for file in files: filePath = os.path.join(root, file).removeprefix(projectRootPath).strip('/') gitFileOwner = None for issue in issues: if fileToIssueMapper.isPathBelongsToIssue(file, filePath, issue): if not gitFileOwner: gitFileOwner = self.findFileOwner(filePath=filePath, gitFileOwners=gitFileOwners, projectRootPath=projectRootPath) mappedIssue = MappedIssueFileModel( fileGitLink='{}/{}'.format(githubMainBranch, filePath.strip('/')), filePath=filePath, issue=issue, fileOwner=gitFileOwner ) mappedArray.append(mappedIssue) mappedArray.sort(key=lambda x: x.issue.total_users, reverse=True) return mappedArray ሁሉም ይህን ጊዜ ማድረግ አለብዎት ነው githubMainBranch ባህሪያት ወደ የእርስዎን አቅርቦት አገናኝ ጋር ማሻሻል. ቀጣይ, እኛ ጥያቄዎች እና ፋይሎች ባለቤትዎች, የኮድ በመጠቀም የሚከተሉት ፋይሎች ማሸግ, እና አንድ መጨረሻ ውጤት ያገኛሉ - በ total_users በከፍተኛ ቅርጸት የተመሠረተ ጥያቄዎች ዝርዝር. mapper = BigQueryCrashesFilesMapper() mappedIssues = mapper.mapBQResultsWithFiles( fileToIssueMapper=IosFileToIssueMapper(), issues=allCrashes, gitFileOwners=allUsers, projectRootPath=projectRootPath ) for issue in mappedIssues: if issue.fileOwner: print(list(issue.fileOwner.nicknames)[0], issue.issue.total_users, issue.issue.issue_title) else: print('no owner', issue.issue.total_users, issue.issue.issue_title) የ Jira ተግባር ለመፍጠር የ faile owner በዚህ ጊዜ, Jira መተግበሪያዎችን ለመፍጠር ለመጀመር ሁሉንም ነገር አለን. ነገር ግን, Jira መተግበሪያዎች በአጠቃላይ ኩባንያዎች ውስጥ የተለያዩ ሊሆን ይችላል - ብጁ መስኮቶች, የሥራ ሂደቶች እና መዳረሻዎች የተለያዩ ሊሆን ይችላል. እኔ የ Jira API ማረጋገጫን ይመዝገቡ እና የግል Python ደንበኞች በመጠቀም እርስዎን መተግበሪያ ጋር ተስማሚነት ለማረጋገጥ ይመዝገቡ. እዚህ የእኛን ልምድ ላይ የተመሠረተ አንዳንድ አስፈላጊ ምክሮች ናቸው: እያንዳንዱ ችግሮች ለመፍጠር አይፈልግም. የ 5-10 ምርጥ ችግሮች ላይ የተመሠረተ ተጠቃሚዎች ቁጥር ወይም የተወሰነ ውጤታማ ደረጃ ላይ የተመሠረተ. የፕሮጀክቱ ሜታታታታታዎችን ይጠበቃል. የተቋቋመ የፕሮጀክቶች መረጃዎችን በባህር መክተቻ ውስጥ ይጠበቃል. BigQuery ይጠቀማል, ውሂብ ለሁሉም ስፕሪት መጫወቻዎች ውስጥ የተወሰነ የፕሮጀክቱ ገጽታዎች ውስጥ ይመዝገቡ. መተግበሪያው የቅርብ ስሪት ውስጥ የጽሑፍ ስሪት ተመልካች ከሆነ መጨረሻው መተግበሪያዎች ይመዝገቡ - ይህ regressions ማረጋገጥ አይችሉም. ተመሳሳይ ችግሮች ላይ መተግበሪያዎችን ያካትታሉ, እርግጠኛ ይሆናል እና የመጨረሻው ምርመራን ለመጠበቅ. የክፍያ ዝርዝር ውስጥ በጣም ብዙ ዝርዝር ያካትታሉ. አጠቃቀም አጠቃቀም አጠቃቀም አጠቃቀም አጠቃቀም አጠቃቀም አጠቃቀም, ስሪቶች, ወዘተ ያካትታሉ. ተመሳሳይ ፋይሎችን ከጀመረ ከሆነ የተያያዙ አገናኞች መቁረጫዎች — ይህ ተጨማሪ ገጽታ ይሰጣል. የእርስዎ ቡድን በ Slack (ወይም ሌላ ማጣሪያ ስርዓት) ውስጥ አዲስ ሥራዎችን ለመፍጠር ወይም የተወሰነ ሥራዎችን ለማሳየት የሚፈልጉትን ጊዜ ያውቃሉ. የ crash report, task, relevant GitHub files, ወዘተ ወደ ተስማሚ አገናኞች ያካትታል. በ Slack መተግበሪያዎ ላይ ስኬት መተግበሪያዎችን ያካትታሉ. የ try/except blocks ይጠቀሙ እና በ Slack መተግበሪያዎ ላይ ስኬት መተግበሪያዎችን ያቀርባል. ለምሳሌ, የ BigQuery crash retrievals local to speed up iteration. አንዳንድ ክወናዎች የጋራ ወይም ክወና ካርታዎች ሊኖረው ይችላል. እነዚህ ሁኔታዎች ውስጥ, እርስዎ የክወና መተግበሪያ መተግበሪያ ይፈልጋሉ, ነገር ግን በእያንዳንዱ ክወና መተግበሪያ ጋር የ Jira ችግሮች ራስ-ሰር ለመፍጠር አስቸጋሪ ነው. መጨረሻው ይህ ሥርዓት በየቀኑ ከሁለት ደቂቃዎች ውስጥ ከሁለት ደቂቃዎች ውስጥ ከሁለት ደቂቃዎች ውስጥ ከሁለት ደቂቃዎች ውስጥ ከሁለት ደቂቃዎች ውስጥ ከሁለት ደቂቃዎች ውስጥ ከሁለት ደቂቃዎች በኋላ ከሁለት ደቂቃዎች በኋላ ከሁለት ደቂቃዎች በኋላ ከሁለት ደቂቃዎች በኋላ ከሁለት ደቂቃዎች በኋላ ከሁለት ደቂቃዎች በኋላ ከሁለት ደቂቃዎች በኋላ ከሁለት ደቂቃዎች በኋላ ከሁለት ደቂቃዎች በኋላ ከሁለት ደቂቃዎች በኋላ ከሁለት ደቂቃዎች በኋላ ከሁለት ደቂቃዎች በኋላ ከሁለት ደቂቃዎች በኋላ ከሁለት ደቂቃዎች በኋላ ከሁለት ደቂቃዎች በኋላ ከሁለት ደቂቃዎች በኋላ ከሁለት ደቂቃዎች በኋላ ከሁለት ደቂቃዎች በኋላ የእርስዎ ቡድን የተመሠረተ የማይታወቁ የቁማር ጥያቄዎች ውስጥ ይምረጡ ከሆነ - ያውቃሉ.